Merge pull request #19 from foretold-app/feature/1103

Feature/1103
This commit is contained in:
Ozzie Gooen 2020-03-04 13:56:15 +00:00 committed by GitHub
commit 73763cdc40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 100 additions and 41 deletions

View File

@ -156,7 +156,8 @@ module DistPlusChart = {
let (yMaxDiscreteDomainFactor, yMaxContinuousDomainFactor) = let (yMaxDiscreteDomainFactor, yMaxContinuousDomainFactor) =
adjustBoth(toDiscreteProbabilityMass); adjustBoth(toDiscreteProbabilityMass);
<DistributionPlot <DistributionPlot
scale={config.log ? "log" : "linear"} xScale={config.xLog ? "log" : "linear"}
yScale={config.yLog ? "log" : "linear"}
height={DistPlusPlotReducer.heightToPix(config.height)} height={DistPlusPlotReducer.heightToPix(config.height)}
minX minX
maxX maxX
@ -191,7 +192,8 @@ module IntegralChart = {
let maxX = integral |> Distributions.Continuous.T.maxX; let maxX = integral |> Distributions.Continuous.T.maxX;
let timeScale = distPlus.unit |> DistTypes.DistributionUnit.toJson; let timeScale = distPlus.unit |> DistTypes.DistributionUnit.toJson;
<DistributionPlot <DistributionPlot
scale={config.log ? "log" : "linear"} xScale={config.xLog ? "log" : "linear"}
yScale={config.yLog ? "log" : "linear"}
height={DistPlusPlotReducer.heightToPix(config.height)} height={DistPlusPlotReducer.heightToPix(config.height)}
minX minX
maxX maxX
@ -244,8 +246,13 @@ let make = (~distPlus: DistTypes.distPlus) => {
<div className="opacity-50 hover:opacity-100"> <div className="opacity-50 hover:opacity-100">
<button <button
className=button className=button
onClick={_ => dispatch(CHANGE_LOG(index))}> onClick={_ => dispatch(CHANGE_X_LOG(index))}>
{(config.log ? "x-log" : "x-linear") |> ReasonReact.string} {(config.xLog ? "x-log" : "x-linear") |> ReasonReact.string}
</button>
<button
className=button
onClick={_ => dispatch(CHANGE_Y_LOG(index))}>
{(config.yLog ? "y-log" : "y-linear") |> ReasonReact.string}
</button> </button>
<button <button
className=button className=button
@ -293,4 +300,4 @@ let make = (~distPlus: DistTypes.distPlus) => {
{state.showParams ? showAsForm(distPlus) : ReasonReact.null} {state.showParams ? showAsForm(distPlus) : ReasonReact.null}
{state.showStats ? table(distPlus, x) : ReasonReact.null} {state.showStats ? table(distPlus, x) : ReasonReact.null}
</div>; </div>;
}; };

View File

@ -1,5 +1,6 @@
type chartConfig = { type chartConfig = {
log: bool, xLog: bool,
yLog: bool,
isCumulative: bool, isCumulative: bool,
height: int, height: int,
}; };
@ -15,7 +16,8 @@ type action =
| CHANGE_SHOW_PARAMS | CHANGE_SHOW_PARAMS
| REMOVE_DIST(int) | REMOVE_DIST(int)
| ADD_DIST | ADD_DIST
| CHANGE_LOG(int) | CHANGE_X_LOG(int)
| CHANGE_Y_LOG(int)
| CHANGE_IS_CUMULATIVE(int, bool) | CHANGE_IS_CUMULATIVE(int, bool)
| HEIGHT_INCREMENT(int) | HEIGHT_INCREMENT(int)
| HEIGHT_DECREMENT(int); | HEIGHT_DECREMENT(int);
@ -42,7 +44,6 @@ let heightToPix =
| _ => 140; | _ => 140;
let distributionReducer = (index, state: list(chartConfig), action) => { let distributionReducer = (index, state: list(chartConfig), action) => {
Js.log3(index, action, state);
switch (action, E.L.get(state, index)) { switch (action, E.L.get(state, index)) {
| (HEIGHT_INCREMENT(_), Some(dist)) => | (HEIGHT_INCREMENT(_), Some(dist)) =>
E.L.update( E.L.update(
@ -58,18 +59,24 @@ let distributionReducer = (index, state: list(chartConfig), action) => {
) )
| (CHANGE_IS_CUMULATIVE(_, isCumulative), Some(dist)) => | (CHANGE_IS_CUMULATIVE(_, isCumulative), Some(dist)) =>
E.L.update({...dist, isCumulative}, index, state) E.L.update({...dist, isCumulative}, index, state)
| (CHANGE_LOG(_), Some(dist)) => | (CHANGE_X_LOG(_), Some(dist)) =>
E.L.update({...dist, log: !dist.log}, index, state) E.L.update({...dist, xLog: !dist.xLog}, index, state)
| (CHANGE_Y_LOG(_), Some(dist)) =>
E.L.update({...dist, yLog: !dist.yLog}, index, state)
| (REMOVE_DIST(_), Some(_)) => E.L.remove(index, 1, state) | (REMOVE_DIST(_), Some(_)) => E.L.remove(index, 1, state)
| (ADD_DIST, Some(_)) => | (ADD_DIST, Some(_)) =>
E.L.append(state, [{log: false, isCumulative: false, height: 2}]) E.L.append(
state,
[{yLog: false, xLog: false, isCumulative: false, height: 2}],
)
| _ => state | _ => state
}; };
}; };
let reducer = (state: state, action: action) => let reducer = (state: state, action: action) =>
switch (action) { switch (action) {
| CHANGE_LOG(i) | CHANGE_X_LOG(i)
| CHANGE_Y_LOG(i)
| CHANGE_IS_CUMULATIVE(i, _) | CHANGE_IS_CUMULATIVE(i, _)
| HEIGHT_DECREMENT(i) | HEIGHT_DECREMENT(i)
| REMOVE_DIST(i) | REMOVE_DIST(i)
@ -89,7 +96,7 @@ let init = {
showStats: false, showStats: false,
showParams: false, showParams: false,
distributions: [ distributions: [
{log: false, isCumulative: false, height: 2}, {yLog: false, xLog: false, isCumulative: false, height: 2},
{log: false, isCumulative: true, height: 1}, {yLog: false, xLog: false, isCumulative: true, height: 1},
], ],
}; };

View File

@ -29,7 +29,8 @@ module RawPlot = {
~onHover=(f: float) => (), ~onHover=(f: float) => (),
~continuous=?, ~continuous=?,
~discrete=?, ~discrete=?,
~scale=?, ~xScale=?,
~yScale=?,
~showDistributionLines=?, ~showDistributionLines=?,
~showDistributionYAxis=?, ~showDistributionYAxis=?,
~showVerticalLine=?, ~showVerticalLine=?,
@ -51,7 +52,8 @@ module RawPlot = {
~onHover, ~onHover,
~continuous?, ~continuous?,
~discrete?, ~discrete?,
~scale?, ~xScale?,
~yScale?,
~showDistributionLines?, ~showDistributionLines?,
~showDistributionYAxis?, ~showDistributionYAxis?,
~showVerticalLine?, ~showVerticalLine?,
@ -116,7 +118,8 @@ let make =
~yMaxContinuousDomainFactor=?, ~yMaxContinuousDomainFactor=?,
~onHover: float => unit=_ => (), ~onHover: float => unit=_ => (),
~continuous=?, ~continuous=?,
~scale=?, ~xScale=?,
~yScale=?,
~showDistributionLines=false, ~showDistributionLines=false,
~showDistributionYAxis=false, ~showDistributionYAxis=false,
~showVerticalLine=false, ~showVerticalLine=false,
@ -128,7 +131,8 @@ let make =
?minX ?minX
?yMaxDiscreteDomainFactor ?yMaxDiscreteDomainFactor
?yMaxContinuousDomainFactor ?yMaxContinuousDomainFactor
?scale ?xScale
?yScale
?timeScale ?timeScale
discrete={discrete |> E.O.fmap(XYShape.toJs)} discrete={discrete |> E.O.fmap(XYShape.toJs)}
height height

View File

@ -32,11 +32,20 @@ export class DistPlotD3 {
xScaleLogBase: 10, xScaleLogBase: 10,
// Y // Y
minY: null,
maxY: null,
yScaleType: 'linear',
yScaleTimeOptions: null,
yScaleLogBase: 10,
xMinContinuousDomainFactor: 1,
xMaxContinuousDomainFactor: 1,
yMaxContinuousDomainFactor: 1, yMaxContinuousDomainFactor: 1,
yMaxDiscreteDomainFactor: 1, yMaxDiscreteDomainFactor: 1,
showDistributionYAxis: false,
showDistributionYAxis: false,
showDistributionLines: true, showDistributionLines: true,
areaColors: ['#E1E5EC', '#E1E5EC'], areaColors: ['#E1E5EC', '#E1E5EC'],
verticalLine: 110, verticalLine: 110,
showVerticalLine: true, showVerticalLine: true,
@ -65,7 +74,7 @@ export class DistPlotD3 {
/** /**
* @param {string} name * @param {string} name
* @param value * @param value
* @returns {CdfChartD3} * @returns {DistPlotD3}
*/ */
set(name, value) { set(name, value) {
_.set(this.attrs, [name], value); _.set(this.attrs, [name], value);
@ -74,7 +83,7 @@ export class DistPlotD3 {
/** /**
* @param data * @param data
* @returns {CdfChartD3} * @returns {DistPlotD3}
*/ */
data(data) { data(data) {
const continuousXs = _.get(data, 'continuous.xs', []); const continuousXs = _.get(data, 'continuous.xs', []);
@ -102,11 +111,18 @@ export class DistPlotD3 {
if (!['log', 'linear'].includes(this.attrs.xScaleType)) { if (!['log', 'linear'].includes(this.attrs.xScaleType)) {
throw new Error('X-scale type should be either "log" or "linear".'); throw new Error('X-scale type should be either "log" or "linear".');
} }
if (!['log', 'linear'].includes(this.attrs.yScaleType)) {
throw new Error('Y-scale type should be either "log" or "linear".');
}
// Log Scale. // Log Scale.
if (this.attrs.xScaleType === 'log') { if (this.attrs.xScaleType === 'log') {
this.logFilter('continuous'); this.logFilter('continuous', (x, y) => x > 0);
this.logFilter('discrete'); this.logFilter('discrete', (x, y) => x > 0);
}
if (this.attrs.yScaleType === 'log') {
this.logFilter('continuous', (x, y) => y > 0);
this.logFilter('discrete', (x, y) => y > 0);
} }
if ( if (
this.attrs.xScaleType === 'log' this.attrs.xScaleType === 'log'
@ -116,6 +132,14 @@ export class DistPlotD3 {
console.warn('minX should be positive.'); console.warn('minX should be positive.');
this.attrs.minX = undefined; this.attrs.minX = undefined;
} }
if (
this.attrs.yScaleType === 'log'
&& this.attrs.minY !== null
&& this.attrs.minY < 0
) {
console.warn('minY should be positive.');
this.attrs.minY = undefined;
}
// Fields. // Fields.
const fields = [ const fields = [
@ -124,7 +148,7 @@ export class DistPlotD3 {
'svgWidth', 'svgHeight', 'svgWidth', 'svgHeight',
'yMaxContinuousDomainFactor', 'yMaxContinuousDomainFactor',
'yMaxDiscreteDomainFactor', 'yMaxDiscreteDomainFactor',
'xScaleLogBase', 'xScaleLogBase', 'yScaleLogBase',
]; ];
for (const field of fields) { for (const field of fields) {
if (!_.isNumber(this.attrs[field])) { if (!_.isNumber(this.attrs[field])) {
@ -212,10 +236,14 @@ export class DistPlotD3 {
if (!_.isFinite(yMax)) throw new Error('yMax is undefined'); if (!_.isFinite(yMax)) throw new Error('yMax is undefined');
// X-domains. // X-domains.
const xMinDomainFactor = _.get(this.attrs, 'xMinContinuousDomainFactor', 1);
const xMaxDomainFactor = _.get(this.attrs, 'xMaxContinuousDomainFactor', 1);
const yMinDomainFactor = _.get(this.attrs, 'yMinContinuousDomainFactor', 1);
const yMaxDomainFactor = _.get(this.attrs, 'yMaxContinuousDomainFactor', 1); const yMaxDomainFactor = _.get(this.attrs, 'yMaxContinuousDomainFactor', 1);
const xMinDomain = xMin;
const xMaxDomain = xMax; const xMinDomain = xMin * xMinDomainFactor;
const yMinDomain = yMin; const xMaxDomain = xMax * xMaxDomainFactor;
const yMinDomain = yMin * yMinDomainFactor;
const yMaxDomain = yMax * yMaxDomainFactor; const yMaxDomain = yMax * yMaxDomainFactor;
// X-scale. // X-scale.
@ -229,9 +257,14 @@ export class DistPlotD3 {
.range([0, this.calc.chartWidth]); .range([0, this.calc.chartWidth]);
// Y-scale. // Y-scale.
const yScale = d3.scaleLinear() const yScale = this.attrs.yScaleType === 'linear'
.domain([yMinDomain, yMaxDomain]) ? d3.scaleLinear()
.range([this.calc.chartHeight, 0]); .domain([yMinDomain, yMaxDomain])
.range([this.calc.chartHeight, 0])
: d3.scaleLog()
.base(this.attrs.yScaleLogBase)
.domain([yMinDomain, yMaxDomain])
.range([this.calc.chartHeight, 0]);
return { return {
xMin, xMax, xMin, xMax,
@ -394,7 +427,7 @@ export class DistPlotD3 {
addLollipopsChart(common) { addLollipopsChart(common) {
const data = this.getDataPoints('discrete'); const data = this.getDataPoints('discrete');
const _yMin = d3.min(this.attrs.data.discrete.ys); const yMin = d3.min(this.attrs.data.discrete.ys);
const yMax = d3.max(this.attrs.data.discrete.ys); const yMax = d3.max(this.attrs.data.discrete.ys);
// X axis. // X axis.
@ -404,15 +437,22 @@ export class DistPlotD3 {
.call(d3.axisBottom(common.xScale)); .call(d3.axisBottom(common.xScale));
// Y-domain. // Y-domain.
const yMinDomainFactor = _.get(this.attrs, 'yMinDiscreteDomainFactor', 1);
const yMaxDomainFactor = _.get(this.attrs, 'yMaxDiscreteDomainFactor', 1); const yMaxDomainFactor = _.get(this.attrs, 'yMaxDiscreteDomainFactor', 1);
const yMinDomain = 0; const yMinDomain = yMin * yMinDomainFactor;
const yMaxDomain = yMax * yMaxDomainFactor; const yMaxDomain = yMax * yMaxDomainFactor;
// Y-scale. // Y-scale.
const yScale = d3.scaleLinear() const yScale = this.attrs.yScaleType === 'linear'
.domain([yMinDomain, yMaxDomain]) ? d3.scaleLinear()
.range([this.calc.chartHeight, 0]); .domain([yMinDomain, yMaxDomain])
.range([this.calc.chartHeight, 0])
: d3.scaleLog()
.base(this.attrs.yScaleLogBase)
.domain([yMinDomain, yMaxDomain])
.range([this.calc.chartHeight, 0]);
//
const yTicks = Math.floor(this.calc.chartHeight / 20); const yTicks = Math.floor(this.calc.chartHeight / 20);
const yAxis = d3.axisLeft(yScale).ticks(yTicks); const yAxis = d3.axisLeft(yScale).ticks(yTicks);
@ -457,7 +497,7 @@ export class DistPlotD3 {
.attr('x1', d => common.xScale(d.x)) .attr('x1', d => common.xScale(d.x))
.attr('x2', d => common.xScale(d.x)) .attr('x2', d => common.xScale(d.x))
.attr('y1', d => yScale(d.y)) .attr('y1', d => yScale(d.y))
.attr('y2', yScale(0)); .attr('y2', yScale(yMin));
// Define the div for the tooltip // Define the div for the tooltip
const tooltip = this._container.append('div') const tooltip = this._container.append('div')
@ -552,9 +592,10 @@ export class DistPlotD3 {
/** /**
* @param {string} key * @param {string} key
* @param {function} pred
* @returns {{x: number[], y: number[]}} * @returns {{x: number[], y: number[]}}
*/ */
logFilter(key) { logFilter(key, pred) {
const xs = []; const xs = [];
const ys = []; const ys = [];
const emptyShape = { xs: [], ys: [] }; const emptyShape = { xs: [], ys: [] };
@ -563,7 +604,7 @@ export class DistPlotD3 {
for (let i = 0, len = data.xs.length; i < len; i++) { for (let i = 0, len = data.xs.length; i < len; i++) {
const x = data.xs[i]; const x = data.xs[i];
const y = data.ys[i]; const y = data.ys[i];
if (x > 0) { if (pred(x, y)) {
xs.push(x); xs.push(x);
ys.push(y); ys.push(y);
} }

View File

@ -21,7 +21,6 @@ function getRandomInt(min, max) {
function DistPlotReact(props) { function DistPlotReact(props) {
const containerRef = React.createRef(); const containerRef = React.createRef();
const key = "cdf-chart-react-" + getRandomInt(0, 1000); const key = "cdf-chart-react-" + getRandomInt(0, 1000);
const scale = props.scale || 'linear';
const style = !!props.width ? { width: props.width + "px" } : {}; const style = !!props.width ? { width: props.width + "px" } : {};
const [sized, { width }] = useSize(() => { const [sized, { width }] = useSize(() => {
@ -49,7 +48,8 @@ function DistPlotReact(props) {
.set('verticalLine', props.verticalLine || 110) .set('verticalLine', props.verticalLine || 110)
.set('showVerticalLine', props.showVerticalLine) .set('showVerticalLine', props.showVerticalLine)
.set('container', containerRef.current) .set('container', containerRef.current)
.set('xScaleType', scale) .set('xScaleType', props.xScale || 'linear')
.set('yScaleType', props.yScale || 'linear')
.set('xScaleTimeOptions', props.timeScale) .set('xScaleTimeOptions', props.timeScale)
.set('yMaxContinuousDomainFactor', props.yMaxContinuousDomainFactor || 1) .set('yMaxContinuousDomainFactor', props.yMaxContinuousDomainFactor || 1)
.set('yMaxDiscreteDomainFactor', props.yMaxDiscreteDomainFactor || 1) .set('yMaxDiscreteDomainFactor', props.yMaxDiscreteDomainFactor || 1)