Skip to content

Commit

Permalink
feat(Visualisations): Adds options to display rows for min, max, aver…
Browse files Browse the repository at this point in the history
…age, and total for visualisations shown as tables. (#1438 - [LL-151](https://learningpool.atlassian.net/browse/LL-151))
  • Loading branch information
h-kanazawa authored and ryasmi committed Sep 10, 2019
1 parent 2cb5d4e commit ac88386
Show file tree
Hide file tree
Showing 64 changed files with 1,895 additions and 1,021 deletions.
1 change: 1 addition & 0 deletions lib/constants/visualise.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const VISUALISE_AXES_PREFIX = 'axes';
export const MAX_CUSTOM_COLORS = 7;
export const RESPONSE_ROWS_LIMIT = 10000;

// Visualisation Types
export const LEADERBOARD = 'LEADERBOARD';
Expand Down
2 changes: 2 additions & 0 deletions lib/models/visualisation.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ const schema = new mongoose.Schema({
statementContents: [statementContents],
hasBeenMigrated: { type: Boolean, default: false },
timezone: { type: String },
showStats: { type: Boolean, default: true },
statsAtBottom: { type: Boolean, default: true },
});

schema.readScopes = _.keys(scopes.USER_SCOPES);
Expand Down
158 changes: 158 additions & 0 deletions ui/src/components/ScrollableTable/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import React, { useEffect, useState, createRef } from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import _ from 'lodash';
import styles from './styles.css';

/**
* @param {React.RefObject} ref
* @returns {number|null}
*/
const refToClientHeight = (ref) => {
if (ref && ref.current && ref.current.clientHeight) {
return ref.current.clientHeight;
}
return null;
};

/**
* @param {(number|null)[]} hs1
* @param {(number|null)[]} hs2
* @returns {boolean}
*/
const areSameHeights = (hs1, hs2) =>
hs1.length === hs2.length && hs1.every((h, i) => h === hs2[i]);

/**
* @param {boolean} fixTHead - default is false
* @param {boolean} fixTFoot - default is false
* @param {array} children
* 1 or 0 thead
* 1 or 0 tbody
* 1 or 0 tfoot
*
* Children of <thead/>, <tbody/>, and <tfoot/> should be <tr/> or an array of <tr/>
* Children of <tr/> should be <th/>, <td/>, or an array of <th/> or <td/>
*/
const ScrollableTable = ({
fixTHead = false,
fixTFoot = false,
children = [],
}) => {
const tHead = children.find(c => c.type === 'thead');
const tBody = children.find(c => c.type === 'tbody');
const tFoot = children.find(c => c.type === 'tfoot');

const trsInTHead = tHead ? (tHead.props.children || []).flat().filter(c => c.type === 'tr') : [];
const trsInTBody = tBody ? (tBody.props.children || []).flat().filter(c => c.type === 'tr') : [];
const trsInTFoot = tFoot ? (tFoot.props.children || []).flat().filter(c => c.type === 'tr') : [];

const tHeadTrsRefs = trsInTHead.map(() => createRef());
const tFootTrsRefs = trsInTFoot.map(() => createRef());

const [tHeadTrsHeights, updateTHeadTrsHeights] = useState([]);
const [tFootTrsHeights, updateTFootTrsHeights] = useState([]);

useEffect(() => {
const tHeadTrsHeightsFromRefs = tHeadTrsRefs.map(refToClientHeight);
if (!areSameHeights(tHeadTrsHeights, tHeadTrsHeightsFromRefs)) {
updateTHeadTrsHeights(tHeadTrsHeightsFromRefs);
}

const tFootTrsHeightsFromRefs = tFootTrsRefs.map(refToClientHeight);
if (!areSameHeights(tFootTrsHeights, tFootTrsHeightsFromRefs)) {
updateTFootTrsHeights(tFootTrsHeightsFromRefs);
}
});

const tHeadTrsTops = tHeadTrsHeights.reduce((acc, height) => {
const previousTop = acc[acc.length - 1];
if (previousTop === null || height === null) {
return [...acc, null];
}
return [...acc, previousTop + height];
}, [0]);

const tFootTrsBottoms = [...tFootTrsHeights].reverse().reduce((acc, height) => {
const previousBottom = acc[0];
if (previousBottom === null || height === null) {
return [null, ...acc];
}
return [previousBottom + height, ...acc];
}, [0]);

const tHeadClass = styles.greyStartingStripe;
const tBodyClass = (trsInTHead.length % 2 === 0) ? styles.greyStartingStripe : styles.whiteStartingStripe;
const tFootClass = ((trsInTHead.length + trsInTBody.length) % 2 === 0) ? styles.greyStartingStripe : styles.whiteStartingStripe;

return (
<div className={styles.tableContainer}>
<table className={`${styles.table} table table-bordered table-striped`}>
{tHead && (
<thead className={tHeadClass}>
{
trsInTHead.map((tr, i) => {
const childStyle = (fixTHead && _.isNumber(tHeadTrsTops[i])) ? { position: 'sticky', top: tHeadTrsTops[i] } : {};
return (
<tr
key={tr.key || i}
ref={tHeadTrsRefs[i]}>
{
tr.props.children
.flat()
.filter(cell => ['td', 'th'].some(t => t === cell.type))
.map((cell, j) => React.createElement(
cell.type,
{
...cell.props,
key: cell.key || `thead-tr-c-${i}-${j}`,
style: childStyle
},
))
}
</tr>
);
})
}
</thead>
)}

{tBody && (
<tbody className={tBodyClass}>
{trsInTBody}
</tbody>
)}

{tFoot && (
<thead className={tFootClass}>
{
trsInTFoot.flat().map((tr, i) => {
const childStyle = (fixTFoot && _.isNumber(tFootTrsBottoms[i + 1])) ? { position: 'sticky', bottom: tFootTrsBottoms[i + 1] } : {};
return (
<tr
key={tr.key || i}
ref={tFootTrsRefs[i]}>
{
tr.props.children
.flat()
.filter(cell => ['td', 'th'].some(t => t === cell.type))
.map((cell, j) => React.createElement(
cell.type,
{
...cell.props,
key: cell.key || `tfoot-tr-c-${i}-${j}`,
style: childStyle
},
))
}
</tr>
);
})
}
</thead>
)}
</table>
</div>
);
};

export default withStyles(styles)(ScrollableTable);
79 changes: 79 additions & 0 deletions ui/src/components/ScrollableTable/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
.tableContainer {
height: 100%;
overflow-x: scroll;
overflow-y: scroll;
}

.table {
border-collapse: separate;
margin: 0;
border: 0;
}

/**
* core.css defines the color of odd-number-th lines
*
* .table-striped > tbody > tr:nth-of-type(odd) {
* background-color: #f9f9f9;
* }
*/
.greyStartingStripe > tr:nth-child(even) > th,
.greyStartingStripe > tr:nth-child(even) > td,
.whiteStartingStripe > tr:nth-child(odd) > th,
.whiteStartingStripe > tr:nth-child(odd) > td {
background-color: #ffffff;
}

.greyStartingStripe > tr:nth-child(odd) > th,
.greyStartingStripe > tr:nth-child(odd) > td,
.whiteStartingStripe > tr:nth-child(even) > th,
.whiteStartingStripe > tr:nth-child(even) > td {
background-color: #f9f9f9;
}

.table > thead > tr > th,
.table > thead > tr > td,
.table > tbody > tr > th,
.table > tbody > tr > td,
.table > tfoot > tr > th,
.table > tfoot > tr > td {
border: 0;
}

.table > thead > tr > th,
.table > thead > tr > td,
.table > tfoot > tr > th,
.table > tfoot > tr > td {
border-top: 1px solid #ddd !important;
border-left: 1px solid #ddd !important;
}

.table > thead > tr:last-child > th,
.table > thead > tr:last-child > td,
.table > tfoot > tr:last-child > th,
.table > tfoot > tr:last-child > td {
border-bottom: 1px solid #ddd !important;
}

.table > thead > tr > th:last-child,
.table > thead > tr > td:last-child,
.table > tfoot > tr > th:last-child,
.table > tfoot > tr > td:last-child {
border-right: 1px solid #ddd !important;
}

.table > tbody > tr > th,
.table > tbody > tr > td {
border-left: 1px solid #ddd !important;
border-bottom: 1px solid #ddd !important;
}

.table > tbody > tr:last-child > th,
.table > tbody > tr:last-child > td {
border-left: 1px solid #ddd !important;
}

.table > tbody > tr > th:last-child,
.table > tbody > tr > td:last-child {
border-right: 1px solid #ddd !important;
}
Loading

0 comments on commit ac88386

Please sign in to comment.