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

View File

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

View File

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

View File

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

View File

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