Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2470 from studentinsights/feature/star-debug
Reading: Debug page for STAR coverage and distributions
- Loading branch information
Showing
14 changed files
with
3,588 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import PropTypes from 'prop-types'; | ||
import React from 'react'; | ||
import _ from 'lodash'; | ||
|
||
// Visual component showing a small histogram, initially made for | ||
// percentiles. | ||
export default class Histogram extends React.Component { | ||
scale(count) { | ||
const {values} = this.props; | ||
return `${Math.ceil(100 * count / values.length)}%`; | ||
} | ||
|
||
render() { | ||
const {range, values, bucketSize, height, style = {}} = this.props; | ||
const buckets = _.groupBy(values, value => bucketSize * Math.floor(value / bucketSize)); | ||
const bucketValues = _.range(range[0], range[1], bucketSize); | ||
return ( | ||
<div className="Histogram" style={{height, ...style}}> | ||
<div style={{position: 'relative', width: '100%', height}}> | ||
{bucketValues.map(bucketFloor => { | ||
const countInBucket = (buckets[bucketFloor] || []).length; | ||
return this.renderBucket(bucketFloor, bucketValues.length, countInBucket); | ||
})} | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
|
||
renderBucket(bucketFloor, bucketsCount, countInBucket) { | ||
if (countInBucket === 0) return null; | ||
|
||
const {bucketSize, values, range, height, innerStyle} = this.props; | ||
const width = (range[1] - range[0]) / bucketsCount; | ||
const yScale = this.props.yScale || 1; | ||
const x = (range[1] - range[0]) * (bucketFloor / range[1]); | ||
const y = Math.ceil(height * countInBucket / values.length) / yScale; | ||
const title = `${bucketFloor}-${bucketFloor+bucketSize} has ${countInBucket} values, ${Math.round(100* countInBucket/values.length)}%`; | ||
|
||
return ( | ||
<div key={bucketFloor} title={title}> | ||
<div style={{ | ||
position: 'absolute', | ||
background: 'blue', | ||
left: `${x}%`, | ||
width: `${width}%`, | ||
bottom: 0, | ||
height: y, | ||
...innerStyle | ||
}}>{'\u00A0'}</div> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
Histogram.propTypes = { | ||
bucketSize: PropTypes.number.isRequired, | ||
range: PropTypes.arrayOf(PropTypes.number).isRequired, | ||
values: PropTypes.arrayOf(PropTypes.number).isRequired, | ||
height: PropTypes.number.isRequired, | ||
style: PropTypes.object, | ||
innerStyle: PropTypes.object, | ||
yScale: PropTypes.number | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import _ from 'lodash'; | ||
import {gradeText} from '../helpers/gradeText'; | ||
|
||
// Render a grid of grade by time, with selection. | ||
export default class GradeTimeGrid extends React.Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { | ||
selection: null | ||
}; | ||
} | ||
|
||
render() { | ||
const {isFlipped} = this.props; | ||
return (isFlipped) | ||
? this.renderFlipped() | ||
: this.renderNormal(); | ||
} | ||
|
||
renderNormal() { | ||
const {selection, grades, intervals, onSelectionChanged, renderCellFn} = this.props; | ||
|
||
return ( | ||
<div> | ||
<table style={{width: '100%'}}> | ||
<thead> | ||
<tr> | ||
<th style={styles.firstColumnCell}></th> | ||
{intervals.map(interval => { | ||
const [year, period] = interval; | ||
return ( | ||
<th style={styles.headCell} key={interval.join('-')}> | ||
<div>{year}</div> | ||
<div>{period}</div> | ||
</th> | ||
); | ||
})} | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{grades.map(grade => ( | ||
<tr key={grade}> | ||
<td style={styles.firstColumnCell}>{gradeText(grade)} now</td> | ||
{intervals.map(interval => { | ||
const [year, period] = interval; | ||
return ( | ||
<td | ||
key={interval.join('-')}> | ||
<div | ||
style={_.isEqual(selection, {year, period, grade}) | ||
? {cursor: 'pointer', padding: 10, border: `2px solid ${selection}`} | ||
: {cursor: 'pointer', padding: 10, border: `2px solid white`} | ||
} | ||
onClick={() => onSelectionChanged({year, period, grade})}> | ||
{renderCellFn({year, period, grade})} | ||
</div> | ||
</td> | ||
); | ||
})} | ||
</tr> | ||
))} | ||
</tbody> | ||
</table> | ||
</div> | ||
); | ||
} | ||
|
||
renderFlipped() { | ||
const {selection, grades, intervals, onSelectionChanged, renderCellFn} = this.props; | ||
|
||
return ( | ||
<div> | ||
<table style={{width: '100%'}}> | ||
<thead> | ||
<tr> | ||
<th style={styles.firstColumnCell}></th> | ||
{grades.map(grade => ( | ||
<th style={styles.headCell} key={grade}> | ||
<td>{gradeText(grade)} now</td> | ||
</th> | ||
))} | ||
</tr> | ||
</thead> | ||
<tbody> | ||
{intervals.map(interval => { | ||
const [year, period] = interval; | ||
return ( | ||
<tr key={interval.join('-')}> | ||
<td style={styles.firstColumnCell}><div>{year} {period}</div></td> | ||
{grades.map(grade => { | ||
return ( | ||
<td | ||
style={{textAlign: 'center'}} | ||
key={grade}> | ||
<div | ||
style={_.isEqual(selection, {year, period, grade}) | ||
? {cursor: 'pointer', padding: 10, border: `2px solid ${selection}`} | ||
: {cursor: 'pointer', padding: 10, border: `2px solid white`} | ||
} | ||
onClick={() => onSelectionChanged({year, period, grade})}> | ||
{renderCellFn({year, period, grade})} | ||
</div> | ||
</td> | ||
); | ||
})} | ||
</tr> | ||
); | ||
})} | ||
</tbody> | ||
</table> | ||
</div> | ||
); | ||
} | ||
} | ||
GradeTimeGrid.propTypes = { | ||
grades: PropTypes.arrayOf(PropTypes.string).isRequired, | ||
intervals: PropTypes.array.isRequired, | ||
renderCellFn: PropTypes.func.isRequired, | ||
selection: PropTypes.object, | ||
onSelectionChanged: PropTypes.func.isRequired, | ||
isFlipped: PropTypes.bool | ||
}; | ||
|
||
const styles = { | ||
firstColumnCell: { | ||
textAlign: 'left' | ||
}, | ||
headCell: { | ||
} | ||
}; |
Oops, something went wrong.