├── README.md
├── dist
└── react-pivot-standalone-4.1.1.min.js
├── example
├── basic.jsx
├── data.json
├── demo.css
├── demo.jsx
├── gh.jsx
└── persist.js
├── index.jsx
├── lib
├── column-control.jsx
├── dimensions.jsx
├── download.js
├── get-value.js
├── partial.js
├── pivot-table.jsx
└── umd.js
├── load.js
├── package-lock.json
├── package.json
├── script
└── random-data.js
└── style.css
/README.md:
--------------------------------------------------------------------------------
1 | # ReactPivot #
2 |
3 | ReactPivot is a data-grid component with pivot-table-like functionality for data display, filtering, and exploration. Can be used without React.
4 |
5 | Demo: [http://davidguttman.github.io/react-pivot/](http://davidguttman.github.io/react-pivot/)
6 |
7 | 
8 |
9 | ## Installation & Usage ##
10 |
11 | Default (Browserify/webpack):
12 |
13 | ```
14 | npm i -S react-pivot
15 | ```
16 |
17 | ```js
18 | var React = require('react')
19 | var ReactPivot = require('react-pivot')
20 |
21 | React.render(
22 | ,
27 | document.body
28 | )
29 | ```
30 |
31 | Classic (no React or Browserify):
32 |
33 | Download [react-pivot-standalone-3.0.0.min.js](https://raw.githubusercontent.com/davidguttman/react-pivot/master/dist/react-pivot-standalone-3.0.0.min.js)
34 |
35 | ```html
36 |
37 |
45 | ```
46 |
47 | Custom (Browserify, no React):
48 |
49 | ```js
50 | var ReactPivot = require('react-pivot/load')
51 |
52 | ReactPivot(document.body, {
53 | rows: rows,
54 | dimensions: dimensions,
55 | reduce: reduce,
56 | calculations: calculations
57 | })
58 |
59 | ```
60 |
61 |
62 | ## Example ##
63 |
64 | ```js
65 | var React = require('react')
66 | var ReactPivot = require('react-pivot')
67 |
68 | React.render(
69 | ,
73 | document.body
74 | )
75 | ```
76 |
77 | `ReactPivot` requires four arguments: `rows`, `dimensions`, `reduce` and `calculations`
78 |
79 | `rows` is your data, just an array of objects:
80 | ```js
81 | var rows = [
82 | {"firstName":"Francisco","lastName":"Brekke","state":"NY","transaction":{"amount":"399.73","date":"2012-02-02T08:00:00.000Z","business":"Kozey-Moore","name":"Checking Account 2297","type":"deposit","account":"82741327"}},
83 | {"firstName":"Francisco","lastName":"Brekke","state":"NY","transaction":{"amount":"768.84","date":"2012-02-02T08:00:00.000Z","business":"Herman-Langworth","name":"Money Market Account 9344","type":"deposit","account":"95753704"}}
84 | ]
85 | ```
86 |
87 | `dimensions` is how you want to group your data. Maybe you want to get the total $$ by `firstName` and have the column title be `First Name`:
88 |
89 | ```js
90 | var dimensions = [
91 | {value: 'firstName', title: 'First Name'}
92 | ]
93 | ```
94 |
95 | `reduce` is how you calculate numbers for each group:
96 |
97 | ```js
98 | var reduce = function(row, memo) {
99 | memo.amountTotal = (memo.amountTotal || 0) + parseFloat(row.transaction.amount)
100 | return memo
101 | }
102 | ```
103 |
104 | `calculations` is how you want to display the calculations done in `reduce`:
105 |
106 | ```js
107 | var calculations = [
108 | {
109 | title: 'Amount', value: 'amountTotal',
110 | template: function(val, row) {
111 | return '$' + val.toFixed(2)
112 | },
113 | sortBy: function(row) {
114 | return isNaN(row.amountTotal) ? 0 : row.amountTotal
115 | }
116 | }
117 | ]
118 | ```
119 |
120 | Plug them in and you're good to go!
121 |
122 | ```js
123 |
124 | // Optional: set a default grouping with "activeDimensions"
125 | React.render(
126 | ,
131 | document.body
132 | )
133 | ```
134 |
135 | See it all together in [example/basic.jsx](https://github.com/davidguttman/react-pivot/blob/master/example/basic.jsx)
136 |
137 | ### Optional Arguments ###
138 | parameter | type | description | default
139 | --------- | ---- | ----------- | -------
140 | compact | boolean | compact rows | false
141 | csvDownloadFileName | string | assign name of document created when user clicks to 'Export CSV' | 'table.csv'
142 | csvTemplateFormat | boolean | apply template formatting to data before csv export | false
143 | defaultStyles | boolean | apply default styles from style.css | true
144 | hiddenColumns | array | columns that should not display | []
145 | nPaginateRows | number | items per page setting | 25
146 | solo | object | item that should be displayed solo | null
147 | sortBy | string | name of column to use for record sort | null
148 | sortDir | string | sort direction, either 'asc' or 'desc' | 'asc'
149 | tableClassName | string | assign css class to table containing react-pivot elements | ''
150 | hideDimensionFilter | boolean | do not render the dimension filter | false
151 | hideRows | function | if provided, rows that are passed to the function will not render unless the return value is true | null
152 |
153 | ### TODO ###
154 |
155 | * Better Pagination
156 | * Responsive Table
157 |
158 | ## License ##
159 |
160 | MIT
161 |
--------------------------------------------------------------------------------
/example/basic.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react')
2 | var ReactDOM = require('react-dom')
3 | var ReactPivot = require('..')
4 |
5 | var rows = require('./data.json')
6 |
7 | // These are your "groups"
8 | // "title" is the title of the column
9 | // all rows with the same "value" will be grouped
10 | var dimensions = [
11 | // "value" can be the key of what you want to group on
12 | {title: 'Last Name', value: 'lastName'},
13 | // "value" can also be function that returns what you want to group on
14 | {
15 | title: 'Transaction Type',
16 | value: function(row) { return row.transaction.type },
17 | template: function(value) {
18 | return ''+value+''
19 | }
20 | }
21 | ]
22 |
23 | // All rows will be run through the "reduce" function
24 | // Use this to build up a "memo" object with properties you're interested in
25 | var reduce = function(row, memo) {
26 | // the memo object starts as {} for each group, build it up
27 | memo.count = (memo.count || 0) + 1
28 | memo.amountTotal = (memo.amountTotal || 0) + parseFloat(row.transaction.amount)
29 | // be sure to return it when you're done for the next pass
30 | return memo
31 | }
32 |
33 | // Calculations are columns for the "memo" object built up above
34 | // "title" is the title of the column
35 | var calculations = [
36 | // "value" can be the key of the "memo" object from reduce
37 | // "template" changes the display of the value, but not sorting behavior
38 | {
39 | title: 'Amount', value: 'amountTotal',
40 | template: function(val, row) { return '$' + val.toFixed(2) }
41 | },
42 | {
43 | title: 'Avg Amount',
44 | // "value" can also be a function
45 | value: function(memo) { return memo.amountTotal / memo.count },
46 | template: function(val, row) { return '$' + val.toFixed(2) },
47 | // you can also give a column a custom class (e.g. right align for numbers)
48 | className: 'alignRight'
49 | }
50 | ]
51 |
52 | ReactDOM.render(
53 | ,
58 | document.body
59 | )
60 |
--------------------------------------------------------------------------------
/example/data.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "firstName": "Francisco",
4 | "lastName": "Brekke",
5 | "state": "NY",
6 | "transaction": {
7 | "amount": "399.73",
8 | "date": "2012-02-02T08:00:00.000Z",
9 | "business": "Kozey-Moore",
10 | "name": "Checking Account 2297",
11 | "type": "deposit",
12 | "account": "82741327"
13 | }
14 | },
15 | {
16 | "firstName": "Francisco",
17 | "lastName": "Brekke",
18 | "state": "NY",
19 | "transaction": {
20 | "amount": "768.84",
21 | "date": "2012-02-02T08:00:00.000Z",
22 | "business": "Herman-Langworth",
23 | "name": "Money Market Account 9344",
24 | "type": "deposit",
25 | "account": "95753704"
26 | }
27 | },
28 | {
29 | "firstName": "Francisco",
30 | "lastName": "Brekke",
31 | "state": "NY",
32 | "transaction": {
33 | "amount": "759.28",
34 | "date": "2012-02-02T08:00:00.000Z",
35 | "business": "Kozey-Moore",
36 | "name": "Auto Loan Account 3984",
37 | "type": "withdrawal",
38 | "account": "93218854"
39 | }
40 | },
41 | {
42 | "firstName": "Francisco",
43 | "lastName": "White",
44 | "state": "VA",
45 | "transaction": {
46 | "amount": "362.88",
47 | "date": "2012-02-02T08:00:00.000Z",
48 | "business": "Rosenbaum-Nicolas",
49 | "name": "Credit Card Account 3199",
50 | "type": "withdrawal",
51 | "account": "63038962"
52 | }
53 | },
54 | {
55 | "firstName": "Francisco",
56 | "lastName": "White",
57 | "state": "VA",
58 | "transaction": {
59 | "amount": "750.30",
60 | "date": "2012-02-02T08:00:00.000Z",
61 | "business": "Armstrong-Bode",
62 | "name": "Checking Account 9926",
63 | "type": "invoice",
64 | "account": "31968241"
65 | }
66 | },
67 | {
68 | "firstName": "Francisco",
69 | "lastName": "White",
70 | "state": "VA",
71 | "transaction": {
72 | "amount": "905.14",
73 | "date": "2012-02-02T08:00:00.000Z",
74 | "business": "Armstrong-Bode",
75 | "name": "Auto Loan Account 1715",
76 | "type": "withdrawal",
77 | "account": "98467516"
78 | }
79 | },
80 | {
81 | "firstName": "Skylar",
82 | "lastName": "Prohaska",
83 | "state": "NY",
84 | "transaction": {
85 | "amount": "164.94",
86 | "date": "2012-02-02T08:00:00.000Z",
87 | "business": "Kozey-Moore",
88 | "name": "Home Loan Account 2759",
89 | "type": "invoice",
90 | "account": "73958452"
91 | }
92 | },
93 | {
94 | "firstName": "Skylar",
95 | "lastName": "Prohaska",
96 | "state": "NY",
97 | "transaction": {
98 | "amount": "929.74",
99 | "date": "2012-02-02T08:00:00.000Z",
100 | "business": "Herman-Langworth",
101 | "name": "Auto Loan Account 8133",
102 | "type": "deposit",
103 | "account": "47065994"
104 | }
105 | },
106 | {
107 | "firstName": "Skylar",
108 | "lastName": "Prohaska",
109 | "state": "NY",
110 | "transaction": {
111 | "amount": "618.11",
112 | "date": "2012-02-02T08:00:00.000Z",
113 | "business": "Bartoletti, Powlowski and Halvorson",
114 | "name": "Checking Account 1064",
115 | "type": "payment",
116 | "account": "75087768"
117 | }
118 | },
119 | {
120 | "firstName": "Price",
121 | "lastName": "Runolfsdottir",
122 | "state": "VA",
123 | "transaction": {
124 | "amount": "102.38",
125 | "date": "2012-02-02T08:00:00.000Z",
126 | "business": "Armstrong-Bode",
127 | "name": "Auto Loan Account 8396",
128 | "type": "deposit",
129 | "account": "75340917"
130 | }
131 | },
132 | {
133 | "firstName": "Price",
134 | "lastName": "Runolfsdottir",
135 | "state": "VA",
136 | "transaction": {
137 | "amount": "139.52",
138 | "date": "2012-02-02T08:00:00.000Z",
139 | "business": "Bartoletti, Powlowski and Halvorson",
140 | "name": "Savings Account 3361",
141 | "type": "invoice",
142 | "account": "99593625"
143 | }
144 | },
145 | {
146 | "firstName": "Price",
147 | "lastName": "Runolfsdottir",
148 | "state": "VA",
149 | "transaction": {
150 | "amount": "719.45",
151 | "date": "2012-02-02T08:00:00.000Z",
152 | "business": "Armstrong-Bode",
153 | "name": "Auto Loan Account 5257",
154 | "type": "withdrawal",
155 | "account": "94341473"
156 | }
157 | },
158 | {
159 | "firstName": "Jose",
160 | "lastName": "Romaguera",
161 | "state": "TX",
162 | "transaction": {
163 | "amount": "242.98",
164 | "date": "2012-02-02T08:00:00.000Z",
165 | "business": "McGlynn, Barton and Dare",
166 | "name": "Investment Account 1905",
167 | "type": "invoice",
168 | "account": "72825452"
169 | }
170 | },
171 | {
172 | "firstName": "Jose",
173 | "lastName": "Romaguera",
174 | "state": "TX",
175 | "transaction": {
176 | "amount": "868.16",
177 | "date": "2012-02-02T08:00:00.000Z",
178 | "business": "Kozey-Moore",
179 | "name": "Home Loan Account 1263",
180 | "type": "payment",
181 | "account": "30317863"
182 | }
183 | },
184 | {
185 | "firstName": "Jose",
186 | "lastName": "Romaguera",
187 | "state": "TX",
188 | "transaction": {
189 | "amount": "180.33",
190 | "date": "2012-02-02T08:00:00.000Z",
191 | "business": "Herman-Langworth",
192 | "name": "Auto Loan Account 1699",
193 | "type": "payment",
194 | "account": "56251788"
195 | }
196 | },
197 | {
198 | "firstName": "Juston",
199 | "lastName": "Carter",
200 | "state": "MT",
201 | "transaction": {
202 | "amount": "373.05",
203 | "date": "2012-02-02T08:00:00.000Z",
204 | "business": "McGlynn, Barton and Dare",
205 | "name": "Checking Account 3019",
206 | "type": "invoice",
207 | "account": "57605253"
208 | }
209 | },
210 | {
211 | "firstName": "Juston",
212 | "lastName": "Carter",
213 | "state": "MT",
214 | "transaction": {
215 | "amount": "681.46",
216 | "date": "2012-02-02T08:00:00.000Z",
217 | "business": "Bartoletti, Powlowski and Halvorson",
218 | "name": "Checking Account 7685",
219 | "type": "invoice",
220 | "account": "57943300"
221 | }
222 | },
223 | {
224 | "firstName": "Juston",
225 | "lastName": "Carter",
226 | "state": "MT",
227 | "transaction": {
228 | "amount": "248.33",
229 | "date": "2012-02-02T08:00:00.000Z",
230 | "business": "Rosenbaum-Nicolas",
231 | "name": "Investment Account 8034",
232 | "type": "withdrawal",
233 | "account": "90015610"
234 | }
235 | },
236 | {
237 | "firstName": "Peyton",
238 | "lastName": "Runolfsdottir",
239 | "state": "NY",
240 | "transaction": {
241 | "amount": "976.49",
242 | "date": "2012-02-02T08:00:00.000Z",
243 | "business": "Rosenbaum-Nicolas",
244 | "name": "Money Market Account 8876",
245 | "type": "invoice",
246 | "account": "19873296"
247 | }
248 | },
249 | {
250 | "firstName": "Peyton",
251 | "lastName": "Runolfsdottir",
252 | "state": "NY",
253 | "transaction": {
254 | "amount": "582.38",
255 | "date": "2012-02-02T08:00:00.000Z",
256 | "business": "McGlynn, Barton and Dare",
257 | "name": "Auto Loan Account 6488",
258 | "type": "withdrawal",
259 | "account": "17785926"
260 | }
261 | },
262 | {
263 | "firstName": "Peyton",
264 | "lastName": "Runolfsdottir",
265 | "state": "NY",
266 | "transaction": {
267 | "amount": "506.56",
268 | "date": "2012-02-02T08:00:00.000Z",
269 | "business": "Bartoletti, Powlowski and Halvorson",
270 | "name": "Credit Card Account 2374",
271 | "type": "payment",
272 | "account": "50008551"
273 | }
274 | },
275 | {
276 | "firstName": "Peyton",
277 | "lastName": "Kunze",
278 | "state": "OK",
279 | "transaction": {
280 | "amount": "43.30",
281 | "date": "2012-02-02T08:00:00.000Z",
282 | "business": "Kozey-Moore",
283 | "name": "Checking Account 4632",
284 | "type": "invoice",
285 | "account": "34814359"
286 | }
287 | },
288 | {
289 | "firstName": "Peyton",
290 | "lastName": "Kunze",
291 | "state": "OK",
292 | "transaction": {
293 | "amount": "991.06",
294 | "date": "2012-02-02T08:00:00.000Z",
295 | "business": "Herman-Langworth",
296 | "name": "Auto Loan Account 0584",
297 | "type": "deposit",
298 | "account": "11464099"
299 | }
300 | },
301 | {
302 | "firstName": "Peyton",
303 | "lastName": "Kunze",
304 | "state": "OK",
305 | "transaction": {
306 | "amount": "577.61",
307 | "date": "2012-02-02T08:00:00.000Z",
308 | "business": "McGlynn, Barton and Dare",
309 | "name": "Home Loan Account 7241",
310 | "type": "invoice",
311 | "account": "41676965"
312 | }
313 | },
314 | {
315 | "firstName": "Vaughn",
316 | "lastName": "Kunze",
317 | "state": "MT",
318 | "transaction": {
319 | "amount": "743.57",
320 | "date": "2012-02-02T08:00:00.000Z",
321 | "business": "McGlynn, Barton and Dare",
322 | "name": "Home Loan Account 3574",
323 | "type": "invoice",
324 | "account": "84819774"
325 | }
326 | },
327 | {
328 | "firstName": "Vaughn",
329 | "lastName": "Kunze",
330 | "state": "MT",
331 | "transaction": {
332 | "amount": "90.66",
333 | "date": "2012-02-02T08:00:00.000Z",
334 | "business": "Armstrong-Bode",
335 | "name": "Savings Account 7557",
336 | "type": "invoice",
337 | "account": "52011601"
338 | }
339 | },
340 | {
341 | "firstName": "Vaughn",
342 | "lastName": "Kunze",
343 | "state": "MT",
344 | "transaction": {
345 | "amount": "800.13",
346 | "date": "2012-02-02T08:00:00.000Z",
347 | "business": "Kozey-Moore",
348 | "name": "Auto Loan Account 6065",
349 | "type": "payment",
350 | "account": "58967211"
351 | }
352 | },
353 | {
354 | "firstName": "Peyton",
355 | "lastName": "Prohaska",
356 | "state": "TX",
357 | "transaction": {
358 | "amount": "498.72",
359 | "date": "2012-02-02T08:00:00.000Z",
360 | "business": "Herman-Langworth",
361 | "name": "Savings Account 1601",
362 | "type": "payment",
363 | "account": "05026480"
364 | }
365 | },
366 | {
367 | "firstName": "Peyton",
368 | "lastName": "Prohaska",
369 | "state": "TX",
370 | "transaction": {
371 | "amount": "454.30",
372 | "date": "2012-02-02T08:00:00.000Z",
373 | "business": "Rosenbaum-Nicolas",
374 | "name": "Investment Account 1660",
375 | "type": "payment",
376 | "account": "33595187"
377 | }
378 | },
379 | {
380 | "firstName": "Peyton",
381 | "lastName": "Prohaska",
382 | "state": "TX",
383 | "transaction": {
384 | "amount": "444.65",
385 | "date": "2012-02-02T08:00:00.000Z",
386 | "business": "Bartoletti, Powlowski and Halvorson",
387 | "name": "Credit Card Account 9966",
388 | "type": "deposit",
389 | "account": "87852484"
390 | }
391 | },
392 | {
393 | "firstName": "Jose",
394 | "lastName": "Romaguera",
395 | "state": "NY",
396 | "transaction": {
397 | "amount": "851.05",
398 | "date": "2012-02-02T08:00:00.000Z",
399 | "business": "Kozey-Moore",
400 | "name": "Credit Card Account 1841",
401 | "type": "withdrawal",
402 | "account": "52589223"
403 | }
404 | },
405 | {
406 | "firstName": "Jose",
407 | "lastName": "Romaguera",
408 | "state": "NY",
409 | "transaction": {
410 | "amount": "89.95",
411 | "date": "2012-02-02T08:00:00.000Z",
412 | "business": "Armstrong-Bode",
413 | "name": "Home Loan Account 3634",
414 | "type": "payment",
415 | "account": "75612090"
416 | }
417 | },
418 | {
419 | "firstName": "Jose",
420 | "lastName": "Romaguera",
421 | "state": "NY",
422 | "transaction": {
423 | "amount": "788.04",
424 | "date": "2012-02-02T08:00:00.000Z",
425 | "business": "Bartoletti, Powlowski and Halvorson",
426 | "name": "Auto Loan Account 7948",
427 | "type": "deposit",
428 | "account": "11625129"
429 | }
430 | },
431 | {
432 | "firstName": "Francisco",
433 | "lastName": "Runolfsdottir",
434 | "state": "OK",
435 | "transaction": {
436 | "amount": "67.82",
437 | "date": "2012-02-02T08:00:00.000Z",
438 | "business": "McGlynn, Barton and Dare",
439 | "name": "Home Loan Account 4231",
440 | "type": "withdrawal",
441 | "account": "05522618"
442 | }
443 | },
444 | {
445 | "firstName": "Francisco",
446 | "lastName": "Runolfsdottir",
447 | "state": "OK",
448 | "transaction": {
449 | "amount": "294.90",
450 | "date": "2012-02-02T08:00:00.000Z",
451 | "business": "Armstrong-Bode",
452 | "name": "Checking Account 7598",
453 | "type": "payment",
454 | "account": "40056161"
455 | }
456 | },
457 | {
458 | "firstName": "Francisco",
459 | "lastName": "Runolfsdottir",
460 | "state": "OK",
461 | "transaction": {
462 | "amount": "221.06",
463 | "date": "2012-02-02T08:00:00.000Z",
464 | "business": "Herman-Langworth",
465 | "name": "Credit Card Account 1537",
466 | "type": "deposit",
467 | "account": "81165023"
468 | }
469 | },
470 | {
471 | "firstName": "Juston",
472 | "lastName": "Prohaska",
473 | "state": "NY",
474 | "transaction": {
475 | "amount": "995.99",
476 | "date": "2012-02-02T08:00:00.000Z",
477 | "business": "Rosenbaum-Nicolas",
478 | "name": "Auto Loan Account 3150",
479 | "type": "payment",
480 | "account": "75527684"
481 | }
482 | },
483 | {
484 | "firstName": "Juston",
485 | "lastName": "Prohaska",
486 | "state": "NY",
487 | "transaction": {
488 | "amount": "453.74",
489 | "date": "2012-02-02T08:00:00.000Z",
490 | "business": "Rosenbaum-Nicolas",
491 | "name": "Auto Loan Account 0759",
492 | "type": "withdrawal",
493 | "account": "39822890"
494 | }
495 | },
496 | {
497 | "firstName": "Juston",
498 | "lastName": "Prohaska",
499 | "state": "NY",
500 | "transaction": {
501 | "amount": "292.59",
502 | "date": "2012-02-02T08:00:00.000Z",
503 | "business": "Bartoletti, Powlowski and Halvorson",
504 | "name": "Investment Account 9848",
505 | "type": "deposit",
506 | "account": "33760924"
507 | }
508 | },
509 | {
510 | "firstName": "Annette",
511 | "lastName": "Carter",
512 | "state": "VA",
513 | "transaction": {
514 | "amount": "696.28",
515 | "date": "2012-02-02T08:00:00.000Z",
516 | "business": "Rosenbaum-Nicolas",
517 | "name": "Money Market Account 1304",
518 | "type": "deposit",
519 | "account": "94830205"
520 | }
521 | },
522 | {
523 | "firstName": "Annette",
524 | "lastName": "Carter",
525 | "state": "VA",
526 | "transaction": {
527 | "amount": "644.96",
528 | "date": "2012-02-02T08:00:00.000Z",
529 | "business": "Herman-Langworth",
530 | "name": "Money Market Account 3918",
531 | "type": "payment",
532 | "account": "36931091"
533 | }
534 | },
535 | {
536 | "firstName": "Annette",
537 | "lastName": "Carter",
538 | "state": "VA",
539 | "transaction": {
540 | "amount": "562.51",
541 | "date": "2012-02-02T08:00:00.000Z",
542 | "business": "Bartoletti, Powlowski and Halvorson",
543 | "name": "Investment Account 5118",
544 | "type": "payment",
545 | "account": "68772278"
546 | }
547 | },
548 | {
549 | "firstName": "Price",
550 | "lastName": "Kunze",
551 | "state": "OK",
552 | "transaction": {
553 | "amount": "236.33",
554 | "date": "2012-02-02T08:00:00.000Z",
555 | "business": "McGlynn, Barton and Dare",
556 | "name": "Home Loan Account 0168",
557 | "type": "withdrawal",
558 | "account": "13816090"
559 | }
560 | },
561 | {
562 | "firstName": "Price",
563 | "lastName": "Kunze",
564 | "state": "OK",
565 | "transaction": {
566 | "amount": "991.23",
567 | "date": "2012-02-02T08:00:00.000Z",
568 | "business": "Bartoletti, Powlowski and Halvorson",
569 | "name": "Auto Loan Account 1395",
570 | "type": "payment",
571 | "account": "91931621"
572 | }
573 | },
574 | {
575 | "firstName": "Price",
576 | "lastName": "Kunze",
577 | "state": "OK",
578 | "transaction": {
579 | "amount": "886.46",
580 | "date": "2012-02-02T08:00:00.000Z",
581 | "business": "Rosenbaum-Nicolas",
582 | "name": "Checking Account 8743",
583 | "type": "withdrawal",
584 | "account": "22895005"
585 | }
586 | },
587 | {
588 | "firstName": "Jose",
589 | "lastName": "Labadie",
590 | "state": "VA",
591 | "transaction": {
592 | "amount": "440.75",
593 | "date": "2012-02-02T08:00:00.000Z",
594 | "business": "Armstrong-Bode",
595 | "name": "Home Loan Account 2377",
596 | "type": "withdrawal",
597 | "account": "10776616"
598 | }
599 | },
600 | {
601 | "firstName": "Jose",
602 | "lastName": "Labadie",
603 | "state": "VA",
604 | "transaction": {
605 | "amount": "916.20",
606 | "date": "2012-02-02T08:00:00.000Z",
607 | "business": "Kozey-Moore",
608 | "name": "Savings Account 0498",
609 | "type": "invoice",
610 | "account": "92178049"
611 | }
612 | },
613 | {
614 | "firstName": "Jose",
615 | "lastName": "Labadie",
616 | "state": "VA",
617 | "transaction": {
618 | "amount": "874.69",
619 | "date": "2012-02-02T08:00:00.000Z",
620 | "business": "Rosenbaum-Nicolas",
621 | "name": "Home Loan Account 6908",
622 | "type": "invoice",
623 | "account": "69693172"
624 | }
625 | },
626 | {
627 | "firstName": "Skylar",
628 | "lastName": "Grant",
629 | "state": "NY",
630 | "transaction": {
631 | "amount": "592.06",
632 | "date": "2012-02-02T08:00:00.000Z",
633 | "business": "Rosenbaum-Nicolas",
634 | "name": "Auto Loan Account 9840",
635 | "type": "invoice",
636 | "account": "06557055"
637 | }
638 | },
639 | {
640 | "firstName": "Skylar",
641 | "lastName": "Grant",
642 | "state": "NY",
643 | "transaction": {
644 | "amount": "759.05",
645 | "date": "2012-02-02T08:00:00.000Z",
646 | "business": "Rosenbaum-Nicolas",
647 | "name": "Auto Loan Account 2210",
648 | "type": "withdrawal",
649 | "account": "77467052"
650 | }
651 | },
652 | {
653 | "firstName": "Skylar",
654 | "lastName": "Grant",
655 | "state": "NY",
656 | "transaction": {
657 | "amount": "664.97",
658 | "date": "2012-02-02T08:00:00.000Z",
659 | "business": "McGlynn, Barton and Dare",
660 | "name": "Checking Account 4950",
661 | "type": "withdrawal",
662 | "account": "09651671"
663 | }
664 | },
665 | {
666 | "firstName": "Francisco",
667 | "lastName": "Labadie",
668 | "state": "TX",
669 | "transaction": {
670 | "amount": "213.09",
671 | "date": "2012-02-02T08:00:00.000Z",
672 | "business": "Herman-Langworth",
673 | "name": "Auto Loan Account 1079",
674 | "type": "deposit",
675 | "account": "03654755"
676 | }
677 | },
678 | {
679 | "firstName": "Francisco",
680 | "lastName": "Labadie",
681 | "state": "TX",
682 | "transaction": {
683 | "amount": "938.41",
684 | "date": "2012-02-02T08:00:00.000Z",
685 | "business": "Rosenbaum-Nicolas",
686 | "name": "Savings Account 2472",
687 | "type": "invoice",
688 | "account": "31248060"
689 | }
690 | },
691 | {
692 | "firstName": "Francisco",
693 | "lastName": "Labadie",
694 | "state": "TX",
695 | "transaction": {
696 | "amount": "251.49",
697 | "date": "2012-02-02T08:00:00.000Z",
698 | "business": "Rosenbaum-Nicolas",
699 | "name": "Auto Loan Account 5555",
700 | "type": "payment",
701 | "account": "09155549"
702 | }
703 | },
704 | {
705 | "firstName": "Jose",
706 | "lastName": "Runolfsdottir",
707 | "state": "MT",
708 | "transaction": {
709 | "amount": "956.85",
710 | "date": "2012-02-02T08:00:00.000Z",
711 | "business": "Kozey-Moore",
712 | "name": "Investment Account 6978",
713 | "type": "invoice",
714 | "account": "68326263"
715 | }
716 | },
717 | {
718 | "firstName": "Jose",
719 | "lastName": "Runolfsdottir",
720 | "state": "MT",
721 | "transaction": {
722 | "amount": "0.11",
723 | "date": "2012-02-02T08:00:00.000Z",
724 | "business": "McGlynn, Barton and Dare",
725 | "name": "Checking Account 5257",
726 | "type": "deposit",
727 | "account": "36570422"
728 | }
729 | },
730 | {
731 | "firstName": "Jose",
732 | "lastName": "Runolfsdottir",
733 | "state": "MT",
734 | "transaction": {
735 | "amount": "33.71",
736 | "date": "2012-02-02T08:00:00.000Z",
737 | "business": "Bartoletti, Powlowski and Halvorson",
738 | "name": "Auto Loan Account 9083",
739 | "type": "payment",
740 | "account": "52055690"
741 | }
742 | },
743 | {
744 | "firstName": "Jose",
745 | "lastName": "Wehner",
746 | "state": "VA",
747 | "transaction": {
748 | "amount": "318.35",
749 | "date": "2012-02-02T08:00:00.000Z",
750 | "business": "Herman-Langworth",
751 | "name": "Investment Account 3128",
752 | "type": "deposit",
753 | "account": "82157760"
754 | }
755 | },
756 | {
757 | "firstName": "Jose",
758 | "lastName": "Wehner",
759 | "state": "VA",
760 | "transaction": {
761 | "amount": "214.23",
762 | "date": "2012-02-02T08:00:00.000Z",
763 | "business": "Kozey-Moore",
764 | "name": "Money Market Account 9764",
765 | "type": "invoice",
766 | "account": "80675575"
767 | }
768 | },
769 | {
770 | "firstName": "Jose",
771 | "lastName": "Wehner",
772 | "state": "VA",
773 | "transaction": {
774 | "amount": "883.50",
775 | "date": "2012-02-02T08:00:00.000Z",
776 | "business": "Kozey-Moore",
777 | "name": "Checking Account 2946",
778 | "type": "deposit",
779 | "account": "68251601"
780 | }
781 | }
782 | ]
783 |
--------------------------------------------------------------------------------
/example/demo.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'helvetica neue';
3 | text-align: center;
4 | background: #f5f5f5;
5 | text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004);
6 | font-weight: 200;
7 | color: #333;
8 | }
9 |
10 | a { color: #0ab; cursor: pointer; }
11 | a:hover {text-decoration: none}
12 |
13 | h1 {
14 | font-weight: 300;
15 | letter-spacing: 1px;
16 | }
17 |
18 | th { font-weight: 400; letter-spacing: 1px; }
19 | td { font-weight: 200 }
20 |
21 | .strong {font-weight: bold}
22 | .hide {display: none}
23 | .alignRight { text-align: right }
24 |
25 | .demo {
26 | width: 750px;
27 | margin: 0 auto;
28 | padding: 20px;
29 | }
30 |
31 | .demo textarea {
32 | width: 710px;
33 | height: 215px;
34 | margin-top: 20px;
35 | border: 0;
36 | padding: 10px 20px 20px;
37 | background: #fff;
38 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
39 | font-family: 'anonymous pro', 'courier', 'monospace'
40 | }
41 |
--------------------------------------------------------------------------------
/example/demo.jsx:
--------------------------------------------------------------------------------
1 | require('./demo.css')
2 |
3 | var React = require('react')
4 | var ReactDOM = require('react-dom')
5 | var createReactClass = require('create-react-class')
6 | var ReactPivot = require('..')
7 |
8 | var gh = require('./gh.jsx')
9 | var data = require('./data.json')
10 |
11 | var dimensions = [
12 | {value: 'firstName', title: 'First Name'},
13 | {value: 'lastName', title: 'Last Name'},
14 | {value: 'state', title: 'State'},
15 | {value: function(row) {
16 | return row.transaction.business
17 | }, title: 'Business'},
18 | {value: function(row) {
19 | return row.transaction.type
20 | }, title: 'Transaction Type'}
21 | ]
22 |
23 | var reduce = function(row, memo) {
24 | memo.count = (memo.count || 0) + 1
25 | memo.amountTotal = (memo.amountTotal || 0) + parseFloat(row.transaction.amount)
26 | return memo
27 | }
28 |
29 | var calculations = [
30 | {
31 | title: 'Count',
32 | value: 'count',
33 | className: 'alignRight',
34 | sortBy: function(row) { return row.count }
35 | },
36 | {
37 | title: 'Amount',
38 | value: 'amountTotal',
39 | template: function(val, row) {
40 | return '$' + val.toFixed(2)
41 | },
42 | className: 'alignRight'
43 | },
44 | {
45 | title: 'Avg Amount',
46 | value: function(row) {
47 | return row.amountTotal / row.count
48 | },
49 | template: function(val, row) {
50 | return '$' + val.toFixed(2)
51 | },
52 | className: 'alignRight'
53 | }
54 | ]
55 |
56 | var hideRows = row => row.amountTotal < 1000
57 |
58 | var Demo = createReactClass({
59 | getInitialState: function() {
60 | return {showInput: false}
61 | },
62 | toggleShow: function() {
63 | var showInput = this.state.showInput
64 | this.setState({showInput: !showInput})
65 | },
66 | render: function() {
67 | return (
68 |
69 |
ReactPivot
70 |
71 |
72 | ReactPivot is a data-grid component with pivot-table-like functionality.
73 |
74 |
75 |
76 | Muggles will love you.
77 |
78 |
79 |
80 |
81 | View project and docs on Github
82 |
83 |
84 |
85 |
86 |
93 |
94 |
95 |
96 |
99 |
100 |
101 |
102 | Grid View
104 | {' | '}
105 | Input Data
107 |
108 |
109 | {gh}
110 |
111 | )
112 | }
113 | })
114 |
115 | var el = document.createElement('div')
116 | document.body.appendChild(el)
117 |
118 | ReactDOM.render(
119 | ,
120 | el
121 | )
122 |
--------------------------------------------------------------------------------
/example/gh.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react')
2 |
3 | var repo = 'davidguttman/react-pivot'
4 |
5 | var style = {
6 | position: 'absolute',
7 | top: 0,
8 | right: 0,
9 | border: 0
10 | }
11 |
12 | module.exports = (
13 |
14 | )
15 |
--------------------------------------------------------------------------------
/example/persist.js:
--------------------------------------------------------------------------------
1 | var React = require('react')
2 | var Emitter = require('wildemitter')
3 | var ReactPivot = require('../load')
4 |
5 | var rows = require('./data.json')
6 |
7 | var dimensions = [
8 | {title: 'Last Name', value: 'lastName'},
9 | {
10 | title: 'Transaction Type',
11 | value: function(row) { return (row.transaction || {}).type },
12 | template: function(value) {
13 | return ''+value+''
14 | }
15 | }
16 | ]
17 |
18 | var reduce = function(row, memo) {
19 | memo.count = (memo.count || 0) + 1
20 | memo.amountTotal = (memo.amountTotal || 0) + parseFloat(row.transaction.amount)
21 |
22 | return memo
23 | }
24 |
25 | var calculations = [
26 | {
27 | title: 'Amount', value: 'amountTotal',
28 | template: function(val, row) { return '$' + val.toFixed(2) }
29 | },
30 | {
31 | title: 'Avg Amount',
32 |
33 | value: function(memo) { return memo.amountTotal / memo.count },
34 | template: function(val, row) { return '$' + val.toFixed(2) },
35 |
36 | className: 'alignRight'
37 | }
38 | ]
39 |
40 | var persisted = JSON.parse(localStorage.rpPersisted || '{}')
41 |
42 | var bus = new Emitter
43 |
44 | var rp = ReactPivot(document.body, {
45 | rows: rows,
46 | dimensions: dimensions,
47 | reduce: reduce,
48 | calculations: calculations,
49 | activeDimensions: persisted.activeDimensions || ['Transaction Type'],
50 | sortBy: persisted.sortBy,
51 | sortDir: persisted.sortDir,
52 | solo: persisted.solo,
53 | hiddenColumns: persisted.hiddenColumns,
54 | eventBus: bus
55 | })
56 |
57 | bus.on('activeDimensions', function(activeDimensions) {
58 | persist('activeDimensions', activeDimensions)
59 | })
60 |
61 | bus.on('sortBy', function(sortBy) {
62 | persist('sortBy', sortBy)
63 | })
64 |
65 | bus.on('sortDir', function(sortDir) {
66 | persist('sortDir', sortDir)
67 | })
68 |
69 | bus.on('hiddenColumns', function(hiddenColumns) {
70 | persist('hiddenColumns', hiddenColumns)
71 | })
72 |
73 | bus.on('solo', function(solo) {
74 | persist('solo', solo)
75 | })
76 |
77 | function persist (prop, val) {
78 | persisted[prop] = val
79 | localStorage.rpPersisted = JSON.stringify(persisted)
80 | }
81 |
--------------------------------------------------------------------------------
/index.jsx:
--------------------------------------------------------------------------------
1 | var _ = {
2 | filter: require('lodash/filter'),
3 | map: require('lodash/map'),
4 | find: require('lodash/find')
5 | }
6 | var React = require('react')
7 | var createReactClass = require('create-react-class')
8 | var DataFrame = require('dataframe')
9 | var Emitter = require('wildemitter')
10 |
11 | var partial = require('./lib/partial')
12 | var download = require('./lib/download')
13 | var getValue = require('./lib/get-value')
14 | var PivotTable = require('./lib/pivot-table.jsx')
15 | var Dimensions = require('./lib/dimensions.jsx')
16 | var ColumnControl = require('./lib/column-control.jsx')
17 |
18 | module.exports = createReactClass({
19 | displayName: 'ReactPivot',
20 | getDefaultProps: function() {
21 | return {
22 | rows: [],
23 | dimensions: [],
24 | activeDimensions: [],
25 | reduce: function() {},
26 | tableClassName: '',
27 | csvDownloadFileName: 'table.csv',
28 | csvTemplateFormat: false,
29 | defaultStyles: true,
30 | nPaginateRows: 25,
31 | solo: {},
32 | hiddenColumns: [],
33 | hideRows: null,
34 | sortBy: null,
35 | sortDir: 'asc',
36 | eventBus: new Emitter,
37 | compact: false,
38 | excludeSummaryFromExport: false,
39 | onData: function () {},
40 | soloText: "solo",
41 | subDimensionText: "Sub Dimension..."
42 | }
43 | },
44 |
45 | getInitialState: function() {
46 | var allDimensions = this.props.dimensions
47 | var activeDimensions = _.filter(this.props.activeDimensions, function (title) {
48 | return _.find(allDimensions, function(col) {
49 | return col.title === title
50 | })
51 | })
52 |
53 | return {
54 | dimensions: activeDimensions,
55 | calculations: {},
56 | sortBy: this.props.sortBy,
57 | sortDir: this.props.sortDir,
58 | hiddenColumns: this.props.hiddenColumns,
59 | solo: this.props.solo,
60 | hideRows: this.props.hideRows,
61 | rows: []
62 | }
63 | },
64 |
65 | componentWillMount: function() {
66 | if (this.props.defaultStyles) loadStyles()
67 |
68 | this.dataFrame = DataFrame({
69 | rows: this.props.rows,
70 | dimensions: this.props.dimensions,
71 | reduce: this.props.reduce
72 | })
73 |
74 | this.updateRows()
75 | },
76 |
77 | componentWillReceiveProps: function(newProps) {
78 | if(newProps.hiddenColumns !== this.props.hiddenColumns) {
79 | this.setHiddenColumns(newProps.hiddenColumns);
80 | }
81 |
82 | if(newProps.rows !== this.props.rows) {
83 | this.dataFrame = DataFrame({
84 | rows: newProps.rows,
85 | dimensions: newProps.dimensions,
86 | reduce: newProps.reduce
87 | })
88 |
89 | this.updateRows()
90 | }
91 | },
92 |
93 | getColumns: function() {
94 | var self = this
95 | var columns = []
96 |
97 | this.state.dimensions.forEach(function(title) {
98 | var d = _.find(self.props.dimensions, function(col) {
99 | return col.title === title
100 | })
101 |
102 | columns.push({
103 | type: 'dimension', title: d.title, value: d.value,
104 | className: d.className, template: d.template, sortBy: d.sortBy
105 | })
106 | })
107 |
108 | this.props.calculations.forEach(function(c) {
109 | if (self.state.hiddenColumns.indexOf(c.title) >= 0) return
110 |
111 | columns.push({
112 | type:'calculation', title: c.title, template: c.template,
113 | value: c.value, className: c.className, sortBy: c.sortBy
114 | })
115 | })
116 |
117 | return columns
118 | },
119 |
120 | render: function() {
121 | var self = this
122 |
123 | var html = (
124 |
125 |
126 | { this.props.hideDimensionFilter ? '' :
127 |
132 | }
133 |
134 |
137 |
138 |
139 |
142 |
143 |
144 | { Object.keys(this.state.solo).map(function (title) {
145 | var value = self.state.solo[title]
146 |
147 | return (
148 |
152 |
155 | ×
156 |
157 | {title}: {value}
158 |
159 | )
160 | }) }
161 |
162 |
174 |
175 | )
176 |
177 | return html
178 | },
179 |
180 | updateRows: function () {
181 | var columns = this.getColumns()
182 |
183 | var sortByTitle = this.state.sortBy
184 | var sortCol = _.find(columns, function(col) {
185 | return col.title === sortByTitle
186 | }) || {}
187 | var sortBy = sortCol.sortBy || (sortCol.type === 'dimension' ? sortCol.title : sortCol.value);
188 | var sortDir = this.state.sortDir
189 | var hideRows = this.state.hideRows
190 |
191 | var calcOpts = {
192 | dimensions: this.state.dimensions,
193 | sortBy: sortBy,
194 | sortDir: sortDir,
195 | compact: this.props.compact
196 | }
197 |
198 | var filter = this.state.solo
199 | if (filter) {
200 | calcOpts.filter = function(dVals) {
201 | var pass = true
202 | Object.keys(filter).forEach(function (title) {
203 | if (dVals[title] !== filter[title]) pass = false
204 | })
205 | return pass
206 | }
207 | }
208 |
209 | var rows = this.dataFrame
210 | .calculate(calcOpts)
211 | .filter(function (row) { return hideRows ? !hideRows(row) : true })
212 | this.setState({rows: rows})
213 | this.props.onData(rows)
214 | },
215 |
216 | setDimensions: function (updatedDimensions) {
217 | this.props.eventBus.emit('activeDimensions', updatedDimensions)
218 | this.setState({dimensions: updatedDimensions})
219 | setTimeout(this.updateRows, 0)
220 | },
221 |
222 | setHiddenColumns: function (hidden) {
223 | this.props.eventBus.emit('hiddenColumns', hidden)
224 | this.setState({hiddenColumns: hidden})
225 | setTimeout(this.updateRows, 0)
226 | },
227 |
228 | setSort: function(cTitle) {
229 | var sortBy = this.state.sortBy
230 | var sortDir = this.state.sortDir
231 | if (sortBy === cTitle) {
232 | sortDir = (sortDir === 'asc') ? 'desc' : 'asc'
233 | } else {
234 | sortBy = cTitle
235 | sortDir = 'asc'
236 | }
237 |
238 | this.props.eventBus.emit('sortBy', sortBy)
239 | this.props.eventBus.emit('sortDir', sortDir)
240 | this.setState({sortBy: sortBy, sortDir: sortDir})
241 | setTimeout(this.updateRows, 0)
242 | },
243 |
244 | setSolo: function(solo) {
245 | var newSolo = this.state.solo
246 | newSolo[solo.title] = solo.value
247 | this.props.eventBus.emit('solo', newSolo)
248 | this.setState({solo: newSolo })
249 | setTimeout(this.updateRows, 0)
250 | },
251 |
252 | clearSolo: function(title) {
253 | var oldSolo = this.state.solo
254 | var newSolo = {}
255 | Object.keys(oldSolo).forEach(function (k) {
256 | if (k !== title) newSolo[k] = oldSolo[k]
257 | })
258 | this.props.eventBus.emit('solo', newSolo)
259 | this.setState({solo: newSolo})
260 | setTimeout(this.updateRows, 0)
261 | },
262 |
263 | hideColumn: function(cTitle) {
264 | var hidden = this.state.hiddenColumns.concat([cTitle])
265 | this.setHiddenColumns(hidden)
266 | setTimeout(this.updateRows, 0)
267 | },
268 |
269 | downloadCSV: function(rows) {
270 | var self = this
271 |
272 | var columns = this.getColumns()
273 |
274 | var csv = _.map(columns, 'title')
275 | .map(JSON.stringify.bind(JSON))
276 | .join(',') + '\n'
277 |
278 | var maxLevel = this.state.dimensions.length - 1
279 | var excludeSummary = this.props.excludeSummaryFromExport
280 |
281 | rows.forEach(function(row) {
282 | if (excludeSummary && (row._level < maxLevel)) return
283 |
284 | var vals = columns.map(function(col) {
285 |
286 | if (col.type === 'dimension') {
287 | var val = row[col.title]
288 | } else {
289 | var val = getValue(col, row)
290 | }
291 |
292 | if (col.template && self.props.csvTemplateFormat) {
293 | val = col.template(val)
294 | }
295 |
296 | return JSON.stringify(val)
297 | })
298 | csv += vals.join(',') + '\n'
299 | })
300 |
301 | download(csv, this.props.csvDownloadFileName, 'text/csv')
302 | }
303 | })
304 |
305 | function loadStyles () { require('./style.css') }
306 |
--------------------------------------------------------------------------------
/lib/column-control.jsx:
--------------------------------------------------------------------------------
1 | var _ = { without: require('lodash/without') }
2 | var React = require('react')
3 | var createReactClass = require('create-react-class')
4 |
5 | module.exports = createReactClass({
6 | getDefaultProps: function () {
7 | return {
8 | hiddenColumns: [],
9 | onChange: function () {}
10 | }
11 | },
12 |
13 | render: function () {
14 | return (
15 |
16 | { !this.props.hiddenColumns.length ? '' :
17 |
23 | }
24 |
25 | )
26 | },
27 |
28 | showColumn: function (evt) {
29 | var col = evt.target.value
30 | var hidden = _.without(this.props.hiddenColumns, col)
31 | this.props.onChange(hidden)
32 | },
33 | })
34 |
--------------------------------------------------------------------------------
/lib/dimensions.jsx:
--------------------------------------------------------------------------------
1 | var _ = { compact: require('lodash/compact') }
2 | var React = require('react')
3 | var createReactClass = require('create-react-class')
4 | var partial = require('./partial')
5 |
6 | module.exports = createReactClass({
7 | getDefaultProps: function () {
8 | return {
9 | dimensions: [],
10 | selectedDimensions: [],
11 | onChange: function () { },
12 | subDimensionText: "Sub Dimension..."
13 | }
14 | },
15 |
16 | render: function () {
17 | var self = this
18 | var subDimensionText = this.props.subDimensionText
19 | var selectedDimensions = this.props.selectedDimensions
20 | var nSelected = selectedDimensions.length
21 |
22 | return (
23 |
24 | {selectedDimensions.map(this.renderDimension)}
25 |
26 |
32 |
33 | )
34 | },
35 |
36 | renderDimension: function(selectedDimension, i) {
37 | return (
38 |
53 | )
54 | },
55 |
56 | toggleDimension: function (iDimension, evt) {
57 | var dimension = evt.target.value
58 | var dimensions = this.props.selectedDimensions
59 |
60 | var curIdx = dimensions.indexOf(dimension)
61 | if (curIdx >= 0) dimensions[curIdx] = null
62 | dimensions[iDimension] = dimension
63 |
64 | var updatedDimensions = _.compact(dimensions)
65 |
66 | this.props.onChange(updatedDimensions)
67 | },
68 | })
69 |
--------------------------------------------------------------------------------
/lib/download.js:
--------------------------------------------------------------------------------
1 | module.exports = function(content, filename, mime) {
2 | if (mime == null) mime = 'text/csv'
3 |
4 | var blob = new Blob([content], { type: mime })
5 |
6 | var a = document.createElement('a')
7 | a.download = filename
8 | a.href = window.URL.createObjectURL(blob)
9 | a.dataset.downloadurl = [mime, a.download, a.href].join(':')
10 |
11 | var e = document.createEvent('MouseEvents')
12 | e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false,
13 | false, false, 0, null)
14 | return a.dispatchEvent(e)
15 | }
16 |
--------------------------------------------------------------------------------
/lib/get-value.js:
--------------------------------------------------------------------------------
1 | module.exports = function getValue (dimension, row) {
2 | if (dimension == null) return null
3 | var val
4 | if (typeof dimension.value === 'string') {
5 | val = row[dimension.value]
6 | } else {
7 | val = dimension.value(row)
8 | }
9 | return val
10 | }
11 |
--------------------------------------------------------------------------------
/lib/partial.js:
--------------------------------------------------------------------------------
1 | var slice = Array.prototype.slice
2 |
3 | module.exports = function (fn) {
4 | var partialArgs = slice.call(arguments, 1)
5 | return function() {
6 | return fn.apply(this, partialArgs.concat(slice.call(arguments)))
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/lib/pivot-table.jsx:
--------------------------------------------------------------------------------
1 | var _ = { range: require('lodash/range') }
2 | var React = require('react')
3 | var createReactClass = require('create-react-class')
4 | var partial = require('./partial')
5 | var getValue = require('./get-value')
6 |
7 | module.exports = createReactClass({
8 |
9 | getDefaultProps: function () {
10 | return {
11 | columns: [],
12 | rows: [],
13 | sortBy: null,
14 | sortDir: 'asc',
15 | onSort: function () {},
16 | onSolo: function () {},
17 | onColumnHide: function () {},
18 | soloText: "solo"
19 | }
20 | },
21 |
22 | getInitialState: function () {
23 | return {
24 | paginatePage: 0
25 | }
26 | },
27 |
28 | render: function() {
29 | var results = this.props.rows
30 |
31 | var paginatedResults = this.paginate(results)
32 |
33 | var tBody = this.renderTableBody(this.props.columns, paginatedResults.rows)
34 | var tHead = this.renderTableHead(this.props.columns)
35 |
36 | return (
37 |
38 |
39 | {tHead}
40 | {tBody}
41 |
42 |
43 | {this.renderPagination(paginatedResults)}
44 |
45 | )
46 | },
47 |
48 | renderTableHead: function(columns) {
49 | var self = this
50 | var sortBy = this.props.sortBy
51 | var sortDir = this.props.sortDir
52 |
53 | return (
54 |
55 |
56 | { columns.map(function(col) {
57 | var className = col.className
58 | if (col.title === sortBy) className += ' ' + sortDir
59 |
60 | var hide = ''
61 | if (col.type !== 'dimension') hide = (
62 |
64 | ×
65 |
66 | )
67 |
68 | return (
69 |
73 |
74 | {hide}
75 | {col.title}
76 | |
77 | )
78 | })}
79 |
80 |
81 | )
82 | },
83 |
84 | renderTableBody: function(columns, rows) {
85 | var self = this
86 |
87 | return (
88 |
89 | {rows.map(function(row) {
90 | return (
91 |
92 | {columns.map(function(col, i) {
93 | if (i < row._level) return |
94 |
95 | return self.renderCell(col, row)
96 | })}
97 |
98 | )
99 |
100 | })}
101 |
102 | )
103 | },
104 |
105 | renderCell: function(col, row) {
106 | if (col.type === 'dimension') {
107 | var val = row[col.title]
108 | var text = val
109 | var dimensionExists = (typeof val) !== 'undefined'
110 | if (col.template && dimensionExists) text = col.template(val, row)
111 | } else {
112 | var val = getValue(col, row)
113 | var text = val
114 | if (col.template) text = col.template(val, row)
115 | }
116 |
117 | if (dimensionExists) {
118 | var solo = (
119 |
120 | {this.props.soloText}
125 |
126 | )
127 | }
128 |
129 | var cell = React.isValidElement(text) ? (
130 | {text}
131 | ) : (
132 |
135 | )
136 |
137 | return(
138 |
141 | {cell}{solo}
142 | |
143 | )
144 | },
145 |
146 | renderPagination: function(pagination) {
147 | var self = this
148 | var nPaginatePages = pagination.nPages
149 | var paginatePage = pagination.curPage
150 |
151 | if (nPaginatePages === 1) return ''
152 |
153 | return (
154 |
155 | {_.range(0, nPaginatePages).map(function(n) {
156 | var c = 'reactPivot-pageNumber'
157 | if (n === paginatePage) c += ' is-selected'
158 | return (
159 |
160 | {n+1}
161 |
162 | )
163 | })}
164 |
165 | )
166 | },
167 |
168 | paginate: function(results) {
169 | if (results.length <= 0) return {rows: results, nPages: 1, curPage: 0}
170 |
171 | var paginatePage = this.state.paginatePage
172 | var nPaginateRows = this.props.nPaginateRows
173 | if (!nPaginateRows || !isFinite(nPaginateRows)) nPaginateRows = results.length
174 |
175 | var nPaginatePages = Math.ceil(results.length / nPaginateRows)
176 | if (paginatePage >= nPaginatePages) paginatePage = nPaginatePages - 1
177 |
178 | var iBoundaryRow = paginatePage * nPaginateRows
179 |
180 | var boundaryLevel = results[iBoundaryRow]._level
181 | var parentRows = []
182 | if (boundaryLevel > 0) {
183 | for (var i = iBoundaryRow-1; i >= 0; i--) {
184 | if (results[i]._level < boundaryLevel) {
185 | parentRows.unshift(results[i])
186 | boundaryLevel = results[i]._level
187 | }
188 | if (results[i]._level === 9) break
189 | }
190 | }
191 |
192 | var iEnd = iBoundaryRow + nPaginateRows
193 | var rows = parentRows.concat(results.slice(iBoundaryRow, iEnd))
194 |
195 | return {rows: rows, nPages: nPaginatePages, curPage: paginatePage}
196 | },
197 |
198 | setPaginatePage: function(nPage) {
199 | this.setState({paginatePage: nPage})
200 | }
201 | })
202 |
203 |
--------------------------------------------------------------------------------
/lib/umd.js:
--------------------------------------------------------------------------------
1 | var LoadReactPivot = require('../load')
2 |
3 | var root = window || this
4 |
5 | if (typeof define === 'function' && define.amd) {
6 | // AMD
7 | define(['ReactPivot'], LoadReactPivot)
8 | } else {
9 | // Global Variables
10 | root.ReactPivot = LoadReactPivot
11 | }
12 |
--------------------------------------------------------------------------------
/load.js:
--------------------------------------------------------------------------------
1 | var React = require('react')
2 | var ReactDOM = require('react-dom')
3 | var ReactPivot = require('./index.jsx')
4 |
5 | module.exports = function (el, opts) {
6 | ReactDOM.render(
7 | React.createElement(ReactPivot, opts),
8 | el
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-pivot",
3 | "description": "React-Pivot is a data-grid component with pivot-table-like functionality for data display, filtering, and exploration.",
4 | "version": "4.4.0",
5 | "author": "David Guttman (http://davidguttman.com/)",
6 | "browser": "index.jsx",
7 | "browserify": {
8 | "transform": [
9 | "reactify",
10 | "envify",
11 | "cssify"
12 | ]
13 | },
14 | "bugs": {
15 | "url": "https://github.com/davidguttman/react-pivot/issues"
16 | },
17 | "dependencies": {
18 | "create-react-class": "^15.6.0",
19 | "cssify": "^0.7.0",
20 | "dataframe": "^2.0.1",
21 | "envify": "^3.2.0",
22 | "lodash": "^4.1.0",
23 | "react": "^0.14.7",
24 | "react-dom": "^0.14.7",
25 | "reactify": "^1.0.0",
26 | "wildemitter": "^1.0.1",
27 | "xtend": "^4.0.0"
28 | },
29 | "devDependencies": {
30 | "browserify": "^8.1.3",
31 | "budo": "^8.0.3",
32 | "faker": "^2.1.2",
33 | "uglify-js": "^2.4.16"
34 | },
35 | "directories": {
36 | "example": "example"
37 | },
38 | "homepage": "https://github.com/davidguttman/react-pivot",
39 | "keywords": [
40 | "data",
41 | "excel",
42 | "grid",
43 | "pagination",
44 | "pivot",
45 | "react",
46 | "react-component",
47 | "sort"
48 | ],
49 | "license": "MIT",
50 | "main": "index.js",
51 | "repository": {
52 | "type": "git",
53 | "url": "https://github.com/davidguttman/react-pivot.git"
54 | },
55 | "scripts": {
56 | "example": "budo example/demo.jsx --live",
57 | "example-basic": "budo example/basic.jsx",
58 | "example-persist": "budo example/persist.js",
59 | "dist": "npm run remove-dist && npm run build-standalone",
60 | "remove-dist": "rm dist/*.js",
61 | "build-standalone": "NODE_ENV=production browserify lib/umd.js | uglifyjs -mc > dist/react-pivot-standalone-${npm_package_version}.min.js"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/script/random-data.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore')
2 | var faker = require('faker')
3 | var xtend = require('xtend')
4 |
5 | var nEntries = 60
6 |
7 | var nPeople = 20
8 | var nFirsts = nPeople / 2
9 | var nLasts = nPeople / 2
10 | var nStates = nPeople / 4
11 | var nTransactionsPerPerson = nEntries/nPeople
12 | var nCompanies = nEntries / 10
13 |
14 | var firstNames = []
15 | var lastNames = []
16 | var states = []
17 | var companies = []
18 |
19 | for (var i = 0; i < nFirsts; i++) firstNames.push(faker.name.firstName())
20 | for (var i = 0; i < nLasts; i++) lastNames.push(faker.name.lastName())
21 | for (var i = 0; i < nStates; i++) states.push(faker.address.stateAbbr())
22 | for (var i = 0; i < nCompanies; i++) companies.push(faker.company.companyName())
23 |
24 | var data = []
25 |
26 | for (var i = 0; i < nPeople; i++) {
27 | var person = {
28 | firstName: _.sample(firstNames),
29 | lastName: _.sample(lastNames),
30 | state: _.sample(states)
31 | }
32 |
33 | for (var j = 0; j < nTransactionsPerPerson; j++) {
34 | var transaction = faker.helpers.createTransaction()
35 | transaction.business = _.sample(companies)
36 | data.push(xtend(person, {transaction: transaction}))
37 | }
38 | }
39 |
40 | console.log(JSON.stringify(data, null, 2))
41 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | .reactPivot {
2 | margin-top: 40px;
3 | padding: 10px 20px 20px;
4 | background: #fff;
5 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
6 | }
7 |
8 | .reactPivot-soloDisplay {
9 | padding: 5px;
10 | }
11 |
12 | .reactPivot-clearSolo {
13 | opacity: 0.5;
14 | cursor: pointer;
15 | font-size: 120%;
16 | margin-right: 2px;
17 | }
18 | .reactPivot-clearSolo:hover {
19 | font-weight: bold;
20 | }
21 |
22 | .reactPivot select {
23 | color: #555;
24 | height: 28px;
25 | border: none;
26 | margin-right: 5px;
27 | margin-top: 5px;
28 | background-color: #FFF;
29 | border: 1px solid #CCC;
30 | }
31 |
32 | .reactPivot-results table {
33 | width: 100%;
34 | clear: both;
35 | text-align: left;
36 | border-spacing: 0;
37 | }
38 |
39 | .reactPivot-results th.asc:after,
40 | .reactPivot-results th.desc:after {
41 | font-size: 50%;
42 | opacity: 0.5;
43 | }
44 |
45 | .reactPivot-results th.asc:after { content: ' ▲' }
46 | .reactPivot-results th.desc:after { content: ' ▼' }
47 |
48 | .reactPivot-results td {
49 | border-top: 1px solid #ddd;
50 | padding: 8px;
51 | }
52 |
53 | .reactPivot-results td.reactPivot-indent {
54 | border: none;
55 | }
56 |
57 | .reactPivot-results tr:hover td {
58 | background: #f5f5f5
59 | }
60 |
61 | .reactPivot-results tr:hover td.reactPivot-indent {
62 | background: none;
63 | }
64 |
65 | .reactPivot-solo {opacity: 0}
66 | .reactPivot-solo:hover {font-weight: bold}
67 | td:hover .reactPivot-solo {opacity: 0.5}
68 |
69 | .reactPivot-csvExport,
70 | .reactPivot-columnControl {
71 | float: right;
72 | margin-left: 5px;
73 | }
74 |
75 | .reactPivot-csvExport button {
76 | background-color: #FFF;
77 | border: 1px solid #CCC;
78 | height: 28px;
79 | color: #555;
80 | cursor: pointer;
81 | padding: 0 10px;
82 | border-radius: 4px;
83 | margin-top: 5px;
84 | }
85 |
86 | .reactPivot-dimensions {
87 | float: left;
88 | padding: 10px 0;
89 | text-align: left;
90 | }
91 |
92 | .reactPivot-hideColumn { opacity: 0 }
93 |
94 | th:hover .reactPivot-hideColumn {
95 | opacity: 0.5;
96 | margin-right: 4px;
97 | margin-bottom: 2px;
98 | }
99 |
100 | .reactPivot-hideColumn:hover {
101 | font-weight: bold;
102 | cursor: pointer;
103 | }
104 |
105 | .reactPivot-pageNumber {
106 | padding: 2px;
107 | margin: 4px;
108 | cursor: pointer;
109 | color: gray;
110 | font-size: 14px;
111 | }
112 |
113 | .reactPivot-pageNumber:hover {
114 | font-weight: bold;
115 | border-bottom: black solid 1px;
116 | color: black;
117 | }
118 |
119 | .reactPivot-pageNumber.is-selected {
120 | font-weight: bold;
121 | border-bottom: black solid 1px;
122 | color: black;
123 | }
124 |
125 | .reactPivot-paginate {
126 | margin-top: 24px;
127 | }
128 |
--------------------------------------------------------------------------------