Merged with master

This commit is contained in:
Ozzie Gooen 2020-02-26 09:18:43 +00:00
commit 1d85c21e91
4 changed files with 209 additions and 156 deletions

View File

@ -1,8 +1,92 @@
type route =
| EAFunds
| GlobalCatastrophe
| Home
| NotFound;
let routeToPath = route =>
switch (route) {
| EAFunds => "/ea-funds"
| GlobalCatastrophe => "/global-catastrophe"
| Home => "/"
| _ => "/"
};
module Menu = {
module Styles = {
open Css;
let menu =
style([
position(`relative),
marginTop(em(0.25)),
marginBottom(em(0.25)),
selector(
"a",
[
borderRadius(em(0.25)),
display(`inlineBlock),
backgroundColor(`hex("eee")),
padding(em(1.)),
cursor(`pointer),
],
),
selector("a:hover", [backgroundColor(`hex("bfcad4"))]),
selector("a:hover", [backgroundColor(`hex("bfcad4"))]),
selector(
"a:not(:first-child):not(:last-child)",
[marginRight(em(0.25)), marginLeft(em(0.25))],
),
]);
};
module Item = {
[@react.component]
let make = (~href, ~children) => {
<a
href
onClick={e => {
e->ReactEvent.Synthetic.preventDefault;
ReasonReactRouter.push(href);
}}>
children
</a>;
};
};
[@react.component] [@react.component]
let make = () => { let make = () => {
<div className="w-full max-w-screen-xl mx-auto px-6"> <div className=Styles.menu>
<FormBuilder.ModelForm model=EAFunds.Interface.model /> <Item href={routeToPath(Home)} key="home"> {"Home" |> E.ste} </Item>
<FormBuilder.ModelForm model=GlobalCatastrophe.Interface.model /> <Item href={routeToPath(EAFunds)} key="ea-funds">
<FormBuilder.ModelForm model=Human.Interface.model /> {"EA Funds" |> E.ste}
</Item>
<Item href={routeToPath(GlobalCatastrophe)} key="gc">
{"Global Catastrophe" |> E.ste}
</Item>
</div>;
};
};
[@react.component]
let make = () => {
let url = ReasonReactRouter.useUrl();
let routing =
switch (url.path) {
| ["ea-funds"] => EAFunds
| ["global-catastrophe"] => GlobalCatastrophe
| [] => Home
| _ => NotFound
};
<div className="w-full max-w-screen-xl mx-auto px-6">
<Menu />
{switch (routing) {
| EAFunds => <FormBuilder.ModelForm model=EAFunds.Interface.model />
| GlobalCatastrophe =>
<FormBuilder.ModelForm model=GlobalCatastrophe.Interface.model />
| Home => <div> {"Welcome" |> E.ste} </div>
| _ => <div> {"Page is not found" |> E.ste} </div>
}}
</div>; </div>;
}; };

View File

@ -29,6 +29,9 @@ export class CdfChartD3 {
continuous: null, continuous: null,
discrete: null, discrete: null,
}, },
yMaxContinuousDomainFactor: 1,
yMaxDiscreteDomainFactor: 1,
options: {},
onHover: (e) => { onHover: (e) => {
}, },
}; };
@ -47,90 +50,21 @@ export class CdfChartD3 {
this.formatDates = this.formatDates.bind(this); this.formatDates = this.formatDates.bind(this);
} }
svgWidth(svgWidth) { set(name, value) {
this.attrs.svgWidth = svgWidth; _.set(this.attrs, [name], value);
return this;
}
svgHeight(height) {
this.attrs.svgHeight = height;
return this;
}
maxX(maxX) {
this.attrs.maxX = maxX;
return this;
}
minX(minX) {
this.attrs.minX = minX;
return this;
}
scale(scale) {
this.attrs.scale = scale;
return this;
}
timeScale(timeScale) {
this.attrs.timeScale = timeScale;
return this;
}
onHover(onHover) {
this.attrs.onHover = onHover;
return this;
}
marginBottom(marginBottom) {
this.attrs.marginBottom = marginBottom;
return this;
}
marginLeft(marginLeft) {
this.attrs.marginLeft = marginLeft;
return this;
}
marginRight(marginRight) {
this.attrs.marginRight = marginRight;
return this;
}
marginTop(marginTop) {
this.attrs.marginTop = marginTop;
return this;
}
showDistributionLines(showDistributionLines) {
this.attrs.showDistributionLines = showDistributionLines;
return this;
}
showDistributionYAxis(showDistributionYAxis) {
this.attrs.showDistributionYAxis = showDistributionYAxis;
return this;
}
verticalLine(verticalLine) {
this.attrs.verticalLine = verticalLine;
return this;
}
showVerticalLine(showVerticalLine) {
this.attrs.showVerticalLine = showVerticalLine;
return this;
}
container(container) {
this.attrs.container = container;
return this; return this;
} }
data(data) { data(data) {
this.attrs.data = data; this.attrs.data = data;
this.attrs.data.continuous = data.continuous || {xs: [], ys: []}; this.attrs.data.continuous = data.continuous || {
this.attrs.data.discrete = data.discrete || {xs: [], ys: []}; xs: [],
ys: [],
};
this.attrs.data.discrete = data.discrete || {
xs: [],
ys: [],
};
return this; return this;
} }
@ -149,8 +83,12 @@ export class CdfChartD3 {
// Calculated properties. // Calculated properties.
this.calc.chartLeftMargin = this.attrs.marginLeft; this.calc.chartLeftMargin = this.attrs.marginLeft;
this.calc.chartTopMargin = this.attrs.marginTop; this.calc.chartTopMargin = this.attrs.marginTop;
this.calc.chartWidth = this.attrs.svgWidth - this.attrs.marginRight - this.attrs.marginLeft; this.calc.chartWidth = this.attrs.svgWidth
this.calc.chartHeight = this.attrs.svgHeight - this.attrs.marginBottom - this.attrs.marginTop; - this.attrs.marginRight
- this.attrs.marginLeft;
this.calc.chartHeight = this.attrs.svgHeight
- this.attrs.marginBottom
- this.attrs.marginTop;
// Add svg. // Add svg.
this.svg = this._container this.svg = this._container
@ -159,12 +97,12 @@ export class CdfChartD3 {
.attr('height', this.attrs.svgHeight) .attr('height', this.attrs.svgHeight)
.attr('pointer-events', 'none'); .attr('pointer-events', 'none');
// Add container g element. // Add container "g" (empty) element.
this.chart = this.svg this.chart = this.svg
.createObject({ tag: 'g', selector: 'chart' }) .createObject({ tag: 'g', selector: 'chart' })
.attr( .attr(
'transform', 'transform',
'translate(' + this.calc.chartLeftMargin + ',' + this.calc.chartTopMargin + ')', `translate(${this.calc.chartLeftMargin}, ${this.calc.chartTopMargin})`,
); );
if (this.hasDate('continuous')) { if (this.hasDate('continuous')) {
@ -186,32 +124,39 @@ export class CdfChartD3 {
const yMin = d3.min(this.attrs.data.continuous.ys); const yMin = d3.min(this.attrs.data.continuous.ys);
const yMax = d3.max(this.attrs.data.continuous.ys); const yMax = d3.max(this.attrs.data.continuous.ys);
// Scales. // X-domains.
let xScale = null; const yMaxDomainFactor = _.get(this.attrs, 'yMaxContinuousDomainFactor', 1);
if (this.attrs.scale === 'linear') { const xMinDomain = xMin;
xScale = d3.scaleLinear() const xMaxDomain = xMax;
.domain([xMin, xMax]) const yMinDomain = yMin;
.range([0, this.calc.chartWidth]); const yMaxDomain = yMax * yMaxDomainFactor;
} else {
xScale = d3.scaleLog()
.base(this.attrs.logBase)
.domain([xMin, xMax])
.range([0, this.calc.chartWidth]);
}
// X-scale.
let xScale = this.attrs.scale === 'linear'
? d3.scaleLinear()
.domain([xMinDomain, xMaxDomain])
.range([0, this.calc.chartWidth])
: d3.scaleLog()
.base(this.attrs.logBase)
.domain([xMinDomain, xMaxDomain])
.range([0, this.calc.chartWidth]);
// Y-scale.
const yScale = d3.scaleLinear() const yScale = d3.scaleLinear()
.domain([yMin, yMax]) .domain([yMinDomain, yMaxDomain])
.range([this.calc.chartHeight, 0]); .range([this.calc.chartHeight, 0]);
// Axis generator. // X-axis.
let xAxis = null; let xAxis = null;
if (!!this.attrs.timeScale) { if (!!this.attrs.timeScale) {
const zero = _.get(this.attrs.timeScale, 'zero', moment()); // Calculates the projection on X-axis.
const unit = _.get(this.attrs.timeScale, 'unit', 'years'); const zero = _.get(this.attrs, 'timeScale.zero', moment());
const unit = _.get(this.attrs, 'timeScale.unit', 'years');
const diff = Math.abs(xMax - xMin); const diff = Math.abs(xMax - xMin);
const left = zero.clone().add(xMin, unit); const left = zero.clone().add(xMin, unit);
const right = left.clone().add(diff, unit); const right = left.clone().add(diff, unit);
// X-time-scale.
const xScaleTime = d3.scaleTime() const xScaleTime = d3.scaleTime()
.domain([left.toDate(), right.toDate()]) .domain([left.toDate(), right.toDate()])
.nice() .nice()
@ -237,6 +182,7 @@ export class CdfChartD3 {
}); });
} }
// Y-axis.
const yAxis = d3.axisRight(yScale); const yAxis = d3.axisRight(yScale);
// Objects. // Objects.
@ -250,12 +196,14 @@ export class CdfChartD3 {
.y0(this.calc.chartHeight); .y0(this.calc.chartHeight);
// Add axis. // Add axis.
this.chart.createObject({ tag: 'g', selector: 'x-axis' }) this.chart
.attr('transform', 'translate(0,' + this.calc.chartHeight + ')') .createObject({ tag: 'g', selector: 'x-axis' })
.attr('transform', `translate(0, ${this.calc.chartHeight})`)
.call(xAxis); .call(xAxis);
if (this.attrs.showDistributionYAxis) { if (this.attrs.showDistributionYAxis) {
this.chart.createObject({ tag: 'g', selector: 'y-axis' }) this.chart
.createObject({ tag: 'g', selector: 'y-axis' })
.call(yAxis); .call(yAxis);
} }
@ -313,15 +261,16 @@ export class CdfChartD3 {
function mouseover() { function mouseover() {
const mouse = d3.mouse(this); const mouse = d3.mouse(this);
hoverLine.attr('opacity', 1).attr('x1', mouse[0]).attr('x2', mouse[0]); hoverLine
.attr('opacity', 1)
.attr('x1', mouse[0])
.attr('x2', mouse[0]);
const xValue = xScale.invert(mouse[0]); const xValue = xScale.invert(mouse[0]);
// This used to be here, but doesn't seem important
// const xValue = (mouse[0] > range[0] && mouse[0] < range[1]) ? : 0;
context.attrs.onHover(xValue); context.attrs.onHover(xValue);
} }
function mouseout() { function mouseout() {
hoverLine.attr('opacity', 0) hoverLine.attr('opacity', 0);
} }
this.chart this.chart
@ -338,49 +287,65 @@ export class CdfChartD3 {
return { xScale, yScale }; return { xScale, yScale };
} }
/**
* @param {object} distributionChart
* @param {object} distributionChart.xScale
* @param {object} distributionChart.yScale
*/
addLollipopsChart(distributionChart) { addLollipopsChart(distributionChart) {
const data = this.getDataPoints('discrete'); const data = this.getDataPoints('discrete');
const ys = data.map(item => item.y);
const yMax = d3.max(ys);
// X axis const _yMin = d3.min(this.attrs.data.discrete.ys);
this.chart.append("g") const yMax = d3.max(this.attrs.data.discrete.ys);
.attr("class", 'lollipops-x-axis')
.attr("transform", "translate(0," + this.calc.chartHeight + ")") // X axis.
this.chart.append('g')
.attr('class', 'lollipops-x-axis')
.attr('transform', `translate(0, ${this.calc.chartHeight})`)
.call(d3.axisBottom(distributionChart.xScale)); .call(d3.axisBottom(distributionChart.xScale));
// Y axis // Y-domain.
const yMaxDomainFactor = _.get(this.attrs, 'yMaxDiscreteDomainFactor', 1);
const yMinDomain = 0;
const yMaxDomain = yMax * yMaxDomainFactor;
// Y-scale.
const yScale = d3.scaleLinear() const yScale = d3.scaleLinear()
.domain([0, yMax]) .domain([yMinDomain, yMaxDomain])
.range([this.calc.chartHeight, 0]); .range([this.calc.chartHeight, 0]);
this.chart.append("g") // Adds "g" for an y-axis.
.attr("class", 'lollipops-y-axis') this.chart.append('g')
.attr("transform", "translate(" + this.calc.chartWidth + ",0)") .attr('class', 'lollipops-y-axis')
.attr('transform', `translate(${this.calc.chartWidth}, 0)`)
.call(d3.axisLeft(yScale)); .call(d3.axisLeft(yScale));
// Lines // Lines.
this.chart.selectAll("lollipops-line") this.chart.selectAll('lollipops-line')
.data(data) .data(data)
.enter() .enter()
.append("line") .append('line')
.attr("class", 'lollipops-line') .attr('class', 'lollipops-line')
.attr("x1", d => distributionChart.xScale(d.x)) .attr('x1', d => distributionChart.xScale(d.x))
.attr("x2", d => distributionChart.xScale(d.x)) .attr('x2', d => distributionChart.xScale(d.x))
.attr("y1", d => yScale(d.y)) .attr('y1', d => yScale(d.y))
.attr("y2", yScale(0)); .attr('y2', yScale(0));
// Circles // Circles.
this.chart.selectAll("lollipops-circle") this.chart.selectAll('lollipops-circle')
.data(data) .data(data)
.enter() .enter()
.append("circle") .append('circle')
.attr("class", 'lollipops-circle') .attr('class', 'lollipops-circle')
.attr("cx", d => distributionChart.xScale(d.x)) .attr('cx', d => distributionChart.xScale(d.x))
.attr("cy", d => yScale(d.y)) .attr('cy', d => yScale(d.y))
.attr("r", "4"); .attr('r', '4');
} }
/**
* @param ts
* @returns {string}
*/
formatDates(ts) { formatDates(ts) {
return moment(ts).format("MMMM Do YYYY"); return moment(ts).format("MMMM Do YYYY");
} }
@ -436,8 +401,8 @@ export class CdfChartD3 {
* @returns {boolean} * @returns {boolean}
*/ */
hasDate(key) { hasDate(key) {
const data = _.get(this.attrs.data, key); const xs = _.get(this.attrs, ['data', key, 'xs']);
return !!data; return !!_.size(xs);
} }
} }

View File

@ -34,26 +34,28 @@ function CdfChartReact(props) {
useEffect(() => { useEffect(() => {
new CdfChartD3() new CdfChartD3()
.svgWidth(width) .set('svgWidth', width)
.svgHeight(props.height) .set('svgHeight', props.height)
.maxX(props.maxX) .set('maxX', props.maxX)
.minX(props.minX) .set('minX', props.minX)
.onHover(props.onHover) .set('onHover', props.onHover)
.marginBottom(props.marginBottom || 15) .set('marginBottom',props.marginBottom || 15)
.marginLeft(30) .set('marginLeft', 30)
.marginRight(30) .set('marginRight', 30)
.marginTop(5) .set('marginTop', 5)
.showDistributionLines(props.showDistributionLines) .set('showDistributionLines', props.showDistributionLines)
.showDistributionYAxis(props.showDistributionYAxis) .set('showDistributionYAxis', props.showDistributionYAxis)
.verticalLine(props.verticalLine) .set('verticalLine', props.verticalLine)
.showVerticalLine(props.showVerticalLine) .set('showVerticalLine', props.showVerticalLine)
.container(containerRef.current) .set('container', containerRef.current)
.set('scale', scale)
.set('timeScale', props.timeScale)
.set('yMaxContinuousDomainFactor', 1)
.set('yMaxDiscreteDomainFactor', 1)
.data({ .data({
continuous: props.continuous, continuous: props.continuous,
discrete: props.discrete, discrete: props.discrete,
}) })
.scale(scale)
.timeScale(props.timeScale)
.render(); .render();
}); });

View File

@ -297,3 +297,5 @@ module JsArray = {
); );
let filter = Js.Array.filter; let filter = Js.Array.filter;
}; };
let ste = React.string;