d3-shape#line JavaScript Examples
The following examples show how to use
d3-shape#line.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: AccelerationsChart.js From openeew-dashboard with Apache License 2.0 | 6 votes |
renderPath = (data, axis) => {
/**
* * Merges all axis data into one big array
*/
const rawAcc = data.map((d) => [...d[axis]])
const mergedAcc = [].concat.apply([], rawAcc)
/**
* * Each second of data has 32 readings & there's a maximum 50 seconds
* * of data sent from the mock API. X values are evenly spaced, based
* * on array indices, so maximum value should be 32 x 50 === 1600
*/
const x = scaleLinear()
.domain([1, 32 * 50])
.range([0, VIEWBOX_WIDTH])
const y = scaleLinear().domain(Y_DOMAIN).range([VIEWBOX_HEIGHT, 0])
const lineBuilder = line()
.curve(curveBasis)
.x((d, i) => {
return x(i + 1)
})
.y((d) => y(d))
const renderedPath = lineBuilder(mergedAcc)
return renderedPath
}
Example #2
Source File: connectionCalculator.js From flume with MIT License | 6 votes |
calculateCurve = (from, to) => {
const length = to.x - from.x;
const thirdLength = length / 3;
const curve = line().curve(curveBasis)([
[from.x, from.y],
[from.x + thirdLength, from.y],
[from.x + thirdLength * 2, to.y],
[to.x, to.y]
]);
return curve;
}
Example #3
Source File: sparkline.js From ThreatMapper with Apache License 2.0 | 6 votes |
constructor(props, context) {
super(props, context);
this.x = scaleLinear();
this.y = scaleLinear();
this.line = line()
.x(d => this.x(d.date))
.y(d => this.y(d.value));
}
Example #4
Source File: sparkline.js From ThreatMapper with Apache License 2.0 | 6 votes |
render() {
const dash = 5;
const hasData = this.props.data && this.props.data.length > 0;
const strokeColor = this.props.hovered && hasData
? this.props.hoverColor
: this.props.strokeColor;
const strokeWidth = this.props.strokeWidth * (this.props.hovered ? HOVER_STROKE_MULTIPLY : 1);
const strokeDasharray = hasData ? undefined : `${dash}, ${dash}`;
const radius = this.props.circleRadius * (this.props.hovered ? HOVER_RADIUS_MULTIPLY : 1);
const fillOpacity = this.props.hovered ? 1 : 0.6;
const circleColor = hasData && this.props.hovered ? strokeColor : strokeColor;
const graph = hasData ? this.getGraphData() : this.getEmptyGraphData();
return (
<div title={graph.title}>
<svg width={this.props.width} height={this.props.height}>
<path
className="sparkline"
fill="none"
stroke={strokeColor}
strokeWidth={strokeWidth}
strokeDasharray={strokeDasharray}
d={this.line(graph.data)}
/>
{hasData && (
<circle
className="sparkcircle"
cx={graph.lastX}
cy={graph.lastY}
fill={circleColor}
fillOpacity={fillOpacity}
stroke="none"
r={radius}
/>
)}
</svg>
</div>
);
}
Example #5
Source File: sparkline.js From ThreatMapper with Apache License 2.0 | 6 votes |
initRanges(hasCircle) {
// adjust scales and leave some room for the circle on the right, upper, and lower edge
let circleSpace = MARGIN;
if (hasCircle) {
circleSpace += Math.ceil(this.props.circleRadius * HOVER_RADIUS_MULTIPLY);
}
this.x.range([MARGIN, this.props.width - circleSpace]);
this.y.range([this.props.height - circleSpace, circleSpace]);
this.line.curve(curveLinear);
}
Example #6
Source File: sparklines.js From website with Apache License 2.0 | 5 votes |
StatisticSparkline = ({ data, field, color }) => {
const height = 40
const width = 120
const marginTop = 5
const marginRight = 20
const dates = []
const values = []
const averages = []
data.forEach((item, key) => {
if (key < 14) {
return
}
let sum = 0
for (let i = key; i > key - 7; i -= 1) {
sum += data[i][field]
}
averages.push({
value: sum / 7,
date: DateTime.fromISO(item.date).toJSDate(),
})
})
averages.forEach(item => {
dates.push(item.date)
values.push(item.value)
})
const dateDomain = extent(dates)
const xScaleTime = scaleTime()
.domain(dateDomain)
.range([0, width])
const yMaxEffective = max(values)
const yScale = scaleLinear()
.domain([0, yMaxEffective])
.nice()
.range([height, 0])
const lineFn = line()
.defined(d => !Number.isNaN(d.value) && d.value !== null)
.curve(curveNatural)
.x(d => xScaleTime(d.date))
.y(d => yScale(d.value))
return (
<svg
className={sparklineStyles.sparkline}
viewBox={`0 0 ${width + marginRight} ${height - marginTop}`}
aria-hidden
>
<g transform={`translate(0 ${marginTop})`}>
<defs>
<marker
id={`triangle-${field}`}
refX="10"
refY="6"
markerWidth="30"
markerHeight="30"
markerUnits="userSpaceOnUse"
orient="auto"
>
<path d="M 0 0 12 6 0 12 3 6" style={{ fill: color }} />
</marker>
</defs>
<path
d={lineFn(averages)}
stroke={color}
strokeWidth={3}
fill="none"
markerEnd={`url(#triangle-${field})`}
/>
</g>
</svg>
)
}
Example #7
Source File: connectionCalculator.js From flume with MIT License | 5 votes |
deleteConnection = ({ id }) => {
const line = document.querySelector(`[data-connection-id="${id}"]`);
if (line) line.parentNode.remove();
}
Example #8
Source File: connectionCalculator.js From flume with MIT License | 5 votes |
deleteConnectionsByNodeId = nodeId => {
const lines = document.querySelectorAll(
`[data-output-node-id="${nodeId}"], [data-input-node-id="${nodeId}"]`
);
for (const line of lines) {
line.parentNode.remove();
}
}
Example #9
Source File: connectionCalculator.js From flume with MIT License | 5 votes |
updateConnection = ({ line, from, to }) => {
line.setAttribute("d", calculateCurve(from, to));
}
Example #10
Source File: connectionCalculator.js From flume with MIT License | 5 votes |
createConnections = (nodes, {scale, stageId}, editorId) => {
const stageRef = getStageRef(editorId);
if(stageRef){
const stage = stageRef.getBoundingClientRect();
const stageHalfWidth = stage.width / 2;
const stageHalfHeight = stage.height / 2;
const byScale = value => (1 / scale) * value;
Object.values(nodes).forEach(node => {
if (node.connections && node.connections.inputs) {
Object.entries(node.connections.inputs).forEach(
([inputName, outputs], k) => {
outputs.forEach(output => {
const fromPort = getPortRect(
output.nodeId,
output.portName,
"output"
);
const toPort = getPortRect(node.id, inputName, "input");
const portHalf = fromPort ? fromPort.width / 2 : 0;
if (fromPort && toPort) {
const id = output.nodeId + output.portName + node.id + inputName;
const existingLine = document.querySelector(
`[data-connection-id="${id}"]`
);
if (existingLine) {
updateConnection({
line: existingLine,
from: {
x: byScale(fromPort.x - stage.x + portHalf - stageHalfWidth),
y: byScale(fromPort.y - stage.y + portHalf - stageHalfHeight)
},
to: {
x: byScale(toPort.x - stage.x + portHalf - stageHalfWidth),
y: byScale(toPort.y - stage.y + portHalf - stageHalfHeight)
}
});
} else {
createSVG({
id,
outputNodeId: output.nodeId,
outputPortName: output.portName,
inputNodeId: node.id,
inputPortName: inputName,
from: {
x: byScale(fromPort.x - stage.x + portHalf - stageHalfWidth),
y: byScale(fromPort.y - stage.y + portHalf - stageHalfHeight)
},
to: {
x: byScale(toPort.x - stage.x + portHalf - stageHalfWidth),
y: byScale(toPort.y - stage.y + portHalf - stageHalfHeight)
},
stage: stageRef
});
}
}
});
}
);
}
});
}
}
Example #11
Source File: edge-container.js From ThreatMapper with Apache License 2.0 | 5 votes |
spline = line()
.curve(curveBasis)
.x(d => d.x)
.y(d => d.y)
Example #12
Source File: CurvaturePlot.js From likelihood with MIT License | 4 votes |
CurvatureChart = props => {
const vizRef = useRef(null);
// Stuff
const margin = { top: 20, right: 20, bottom: 30, left: 50 };
const durationTime = 200;
const w = props.width - margin.left - margin.right;
const h = props.width * 0.5 - margin.top - margin.bottom;
const deriv = props.deriv;
const llThetaMLE = props.llThetaMLE;
const llThetaNull = props.llThetaNull;
const test = props.test;
const n = props.n;
const muNull = props.muNull;
// Axes min and max
var xMax, xMin, llTheta;
const sigmaTheta = Math.sqrt(props.sigma2Theta);
xMax = props.muTheta + sigmaTheta * 5;
xMin = props.muTheta - sigmaTheta * 5;
llTheta = 0;
const data1 = useMemo(
() =>
genEstLogLikCurve(
10,
props.muHat,
props.sigma2Hat,
props.muTheta,
props.sigma2Theta
),
[props.width, props.sigma2Hat, props.muHat]
);
const data2 = useMemo(
() =>
genEstLogLikCurve(
n,
props.muHat,
props.sigma2Hat,
props.muTheta,
props.sigma2Theta
),
[n, props.width, props.sigma2Hat, props.muHat]
);
const yMin = min(data1.y.filter(y => isFinite(y)));
const yMax = max(data1.y);
//const yMax = 0.05;
// Create scales
const yScale = scaleLinear()
.domain([yMin, yMax])
.range([h, 0]);
const xScale = scaleLinear()
.domain([xMin, xMax])
.range([0, w]);
// Scales and Axis
const xAxis = axisBottom(xScale);
// Line function
const linex = line()
.x(d => xScale(d[0]))
.y(d => yScale(d[1]));
// Update
useEffect(() => {
createChart(durationTime);
}, [n, props.width]);
const createChart = () => {
const node = vizRef.current;
const gOuter = select(node).attr(
"transform",
"translate(" + margin.left + "," + margin.top + ")"
);
// x Axis
gOuter
.selectAll("g.xAxis")
.data([0])
.enter()
.append("g")
.attr("class", "xAxis");
select(node)
.select("g.xAxis")
.attr("transform", "translate(" + 0 + "," + h + ")")
.call(xAxis);
// x label
gOuter
.selectAll("#x-label")
.data([0])
.enter()
.append("text")
.style("text-anchor", "middle")
.attr("class", "x-label");
select(node)
.selectAll(".x-label")
.attr(
"transform",
"translate(" + w / 2 + " ," + (h + margin.bottom) + ")"
)
.text("μ");
// y label
gOuter
.selectAll("#y-label")
.data([0])
.enter()
.append("text")
.style("text-anchor", "middle")
.attr("id", "y-label");
select(node)
.selectAll("#y-label")
.attr("transform", "rotate(-90)")
.attr("text-anchor", "middle")
.attr("x", -(h / 2))
.attr("y", -40)
.text("Log-Likelihood");
};
const delta = xMax - xMin;
return (
<svg width={props.width} height={props.width * 0.5}>
<g id="outer" ref={vizRef}>
<g className="viz" clipPath="url(#clip)">
<path d={linex(data1.data)} id="logLikReferenceCurve" />
<path d={linex(data2.data)} id="logLikNCurve" />
<line
className={clsx("LRT", test == "LRT" && "highlight")}
x1={xScale(xMin)}
x2={xScale(xMax)}
y1={yScale(llThetaMLE)}
y2={yScale(llThetaMLE)}
/>
<line
className={clsx("LRT", test == "LRT" && "highlight")}
x1={xScale(xMin)}
x2={xScale(xMax)}
y1={yScale(llThetaNull)}
y2={yScale(llThetaNull)}
/>
<line
className={clsx("wald", test == "wald" && "highlight")}
x1={xScale(props.muHat)}
x2={xScale(props.muHat)}
y1={yScale(yMin)}
y2={yScale(yMax)}
/>
<circle
cx={xScale(muNull)}
cy={yScale(llThetaNull)}
r="5"
fill="red"
className="testPointMuNull"
/>
<circle
cx={xScale(props.muHat)}
cy={yScale(llTheta)}
r="5"
className="testPointMu"
/>
</g>
<line
className={clsx("wald", test == "wald" && "highlight")}
x1={xScale(props.muNull)}
x2={xScale(props.muNull)}
y1={yScale(yMin)}
y2={yScale(yMax)}
/>
<line
className={clsx("score", test == "score" && "highlight")}
x1={xScale(props.muNull - delta)}
x2={xScale(props.muNull + delta)}
y1={yScale(llThetaNull - delta * deriv)}
y2={yScale(llThetaNull + delta * deriv)}
/>
</g>
<defs>
<clipPath id="clip">
<rect id="clip-rect" x="0" y="-10" width={w} height={h + 10} />
</clipPath>
</defs>
</svg>
);
}
Example #13
Source File: renderLines.js From react-orgchart with MIT License | 4 votes |
export function renderLines(config) {
const {
svg,
links,
nodeWidth,
nodeHeight,
borderColor,
sourceNode,
treeData,
animationDuration,
} = config;
const parentNode = sourceNode || treeData;
// Select all the links to render the lines
const link = svg.selectAll('path.link').data(links, ({ source, target }) => {
return `${source.data.id}-${target.data.id}`;
});
// Define the angled line function
const angle = line()
.x(d => d.x)
.y(d => d.y)
.curve(curveLinear);
// Enter any new links at the parent's previous position.
const linkEnter = link
.enter()
.insert('path', 'g')
.attr('class', 'link')
.attr('fill', 'none')
.attr('stroke', borderColor)
.attr('stroke-opacity', 1)
.attr('stroke-width', 1.25)
.attr('d', d => {
const linePoints = [
{
x: d.source.x + parseInt(nodeWidth / 2, 10),
y: d.source.y + margin,
},
{
x: d.source.x + parseInt(nodeWidth / 2, 10),
y: d.source.y + margin,
},
{
x: d.source.x + parseInt(nodeWidth / 2, 10),
y: d.source.y + margin,
},
{
x: d.source.x + parseInt(nodeWidth / 2, 10),
y: d.source.y + margin,
},
];
return angle(linePoints);
});
const linkUpdate = linkEnter.merge(link);
// Transition links to their new position.
linkUpdate
.transition()
.duration(animationDuration)
.attr('d', d => {
const linePoints = [
{
x: d.source.x + parseInt(nodeWidth / 2, 10),
y: d.source.y + nodeHeight,
},
{
x: d.source.x + parseInt(nodeWidth / 2, 10),
y: d.target.y - margin,
},
{
x: d.target.x + parseInt(nodeWidth / 2, 10),
y: d.target.y - margin,
},
{
x: d.target.x + parseInt(nodeWidth / 2, 10),
y: d.target.y,
},
];
return angle(linePoints);
});
// Animate the existing links to the parent's new position
link
.exit()
.transition()
.duration(animationDuration)
.attr('d', () => {
const lineNode = config.sourceNode ? config.sourceNode : parentNode;
const linePoints = [
{
x: lineNode.x + parseInt(nodeWidth / 2, 10),
y: lineNode.y + nodeHeight + 2,
},
{
x: lineNode.x + parseInt(nodeWidth / 2, 10),
y: lineNode.y + nodeHeight + 2,
},
{
x: lineNode.x + parseInt(nodeWidth / 2, 10),
y: lineNode.y + nodeHeight + 2,
},
{
x: lineNode.x + parseInt(nodeWidth / 2, 10),
y: lineNode.y + nodeHeight + 2,
},
];
return angle(linePoints);
});
}
Example #14
Source File: LogLikPlot.js From likelihood with MIT License | 4 votes |
logLikCart = props => {
const vizRef = useRef(null);
const dispatch = useContext(VizDispatch);
// Stuff
const margin = { top: 60, right: 20, bottom: 40, left: 50 };
const durationTime = 200;
const w = props.width - margin.left - margin.right;
const h = props.width * 0.4 - margin.top - margin.bottom;
const sample = props.sample;
const sigmaTheta = Math.sqrt(props.sigma2Theta);
const deriv = props.deriv;
const data1 = props.data;
// Axes min and max
var xMax, xMin, llTheta;
if (props.thetaLab == "mu") {
xMax = props.muTheta + sigmaTheta * 5;
xMin = props.muTheta - sigmaTheta * 5;
llTheta = useMemo(() => logLikSum(sample, props.mu, props.sigma2), [
props.mu,
props.sigma2,
props.sample
]);
} else if (props.thetaLab == "sigma") {
const sigma2MLE = props.sigma2Theta;
xMax = sigma2MLE + sigma2MLE * 2;
xMin = sigma2MLE - sigma2MLE * 5;
xMin = xMin < 0 ? 0.1 : xMin;
llTheta = useMemo(() =>
logLikSum(sample, props.mu, props.sigma2, [
props.mu,
props.sigma2,
props.sample
])
);
}
const x_range = range(xMin, xMax, Math.abs(xMax - xMin) / 50);
const newtonParabola = x_range.map(x1 => {
return [
x1,
quadraticApprox(x1 - props.mu, 1, llTheta, deriv, -10 / props.sigma2)
];
});
const yMin = -100;
const yMax = -20;
const [spring, set] = useSpring(() => ({
xy: [props.mu, props.sigma2],
immediate: false,
config: { duration: 500 }
}));
set({ xy: [props.mu, props.sigma2], immediate: !props.animating });
const bind = useDrag(({ movement: [mx, my], first, memo }) => {
const muStart = first ? props.mu : memo[0];
const sigma2Start = first ? props.sigma2 : memo[1];
const mu = xScale.invert(xScale(muStart) + mx);
const sigma2 = yScale.invert(yScale(sigma2Start) + my);
dispatch({
name: "contourDrag",
value: { mu: mu, sigma2: props.sigma2 }
});
return [muStart, sigma2Start];
});
//const yMax = 0.05;
// Create scales
const yScale = scaleLinear()
.domain([yMin, yMax])
.range([h, 0]);
const xScale = scaleLinear()
.domain([xMin, xMax])
.range([0, w]);
// Scales and Axis
const xAxis = axisBottom(xScale);
const yAxis = axisLeft(yScale).ticks(4);
// Line function
const linex = line()
.x(d => xScale(d[0]))
.y(d => yScale(d[1]));
// Update
useEffect(() => {
createChart(durationTime);
}, [props.mu, props.sigma2, w, props.sample]);
const gradientNext = gradientStep(props);
const gradientNextLL = logLikSum(
sample,
gradientNext.points.mu,
props.sigma2
);
// Tooltip
const Tooltip = ({ theta, thetaLab, ll, deriv }) => {
const x = 0;
const y = 0;
const width = 100;
const path = topTooltipPath(width, 40, 10, 10);
const thetaSymb = thetaLab == "mu" ? "mu" : "sigma^2";
const eqLogLik = katex.renderToString(
`\\frac{\\partial}{\\partial \\${thetaSymb}}\\ell = `,
{
displayMode: false,
throwOnError: false
}
);
return (
<g>
<path
d={path}
className="polygonTip"
transform={`translate(${x + margin.left}, ${y + margin.top - 5})`}
/>
<foreignObject
x={x - width / 2 + margin.left}
y={y}
width={width}
height={50}
>
<div className="vizTooltip">
<p>
<span dangerouslySetInnerHTML={{ __html: eqLogLik }} />
{format(".2f")(deriv)}
</p>
</div>
</foreignObject>
</g>
);
};
const createChart = () => {
const node = vizRef.current;
select(node)
.selectAll("g.viz")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x Axis
select(node)
.selectAll("g.xAxis")
.data([0])
.enter()
.append("g")
.attr("class", "xAxis");
select(node)
.select("g.xAxis")
.attr(
"transform",
"translate(" + margin.left + "," + (h + margin.top) + ")"
)
.call(xAxis);
// y Axis
select(node)
.selectAll("g.yAxis")
.data([0])
.enter()
.append("g")
.attr("class", "yAxis");
select(node)
.select("g.yAxis")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(yAxis);
const gViz = select(node)
.selectAll("g.viz")
.data([0])
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x label
gViz
.selectAll(".x-label")
.data([0])
.enter()
.append("text")
.style("text-anchor", "middle")
.attr("class", "x-label MuiTypography-body1");
select(node)
.selectAll(".x-label")
.attr(
"transform",
"translate(" + w / 2 + " ," + (h + margin.bottom - 5) + ")"
)
.text(props.thetaLab == "mu" ? "μ" : "σ²");
// y label
gViz
.selectAll(".y-label")
.data([0])
.enter()
.append("text")
.style("text-anchor", "middle")
.attr("class", "y-label MuiTypography-body2");
select(node)
.selectAll(".y-label")
.attr("transform", "rotate(-90)")
.attr("text-anchor", "middle")
.attr("x", -(h / 2))
.attr("y", -40)
.text(`ℓ(μ, σ² = ${format(".2f")(props.sigma2)})`);
};
const delta = xMax - xMin;
return (
<svg width={props.width} height={props.width * 0.4}>
<g ref={vizRef}>
<g className="viz">
<g clipPath="url(#clipMu)">
<AnimatedPath
data={data1.data}
x={100}
sigma2={props.sigma2}
xScale={xScale}
yScale={yScale}
linex={linex}
mu={props.mu}
sample={sample}
animating={props.animating}
className="LogLikMu"
/>
{props.algo == "newtonRaphson" && (
<AnimatedPath
data={newtonParabola}
x={100}
sigma2={props.sigma2}
xScale={xScale}
yScale={yScale}
linex={linex}
mu={props.mu}
sample={sample}
animating={props.animating}
className="LogLikNewton"
/>
)}
{props.algo == "gradientAscent" && (
<>
<circle
cx={xScale(gradientNext.points.mu)}
cy={yScale(gradientNextLL)}
r="5"
className="logLikNewtonX--approx"
/>
<line
className="LogLikNewton--maxima"
y1={yScale(yMin)}
y2={yScale(gradientNextLL)}
x1={xScale(gradientNext.points.mu)}
x2={xScale(gradientNext.points.mu)}
/>
</>
)}
</g>
</g>
</g>
<g clipPath="url(#clipMu2)">
<animated.g
{...bind()}
transform={spring.xy.interpolate(
(x, y) =>
`translate(${xScale(x)}, ${yScale(logLikSum(sample, x, y))})`
)}
className="draggable"
>
<circle
cx={margin.left}
cy={margin.top}
r="5"
className="logLikX"
/>
<animated.line
className="deriv"
y1={spring.xy.interpolate(
(x, y) =>
margin.top + yScale(yMax - delta * dMu(10, x, props.muHat, y))
)}
y2={spring.xy.interpolate(
(x, y) =>
margin.top + yScale(yMax + delta * dMu(10, x, props.muHat, y))
)}
x1={margin.left + xScale(xMin - delta)}
x2={margin.left + xScale(xMin + delta)}
/>
<Tooltip
theta={props.theta}
thetaLab={props.thetaLab}
ll={llTheta}
deriv={deriv}
/>
</animated.g>
</g>
<defs>
<clipPath id="clipMu">
<rect id="clip-rectMu" x="0" y="-10" width={w} height={h + 10} />
</clipPath>
<clipPath id="clipMu2">
<rect
id="clip-rectMu"
x={margin.left}
y={-margin.bottom}
width={w}
height={h + 100}
/>
</clipPath>
</defs>
</svg>
);
}
Example #15
Source File: LogLikPlotSigma.js From likelihood with MIT License | 4 votes |
OverlapChart = props => {
const vizRef = useRef(null);
const dispatch = useContext(VizDispatch);
// Stuff
const margin = { top: 0, right: 20, bottom: 40, left: 50 };
const durationTime = 200;
const w = props.width * 0.4 - margin.left - margin.right;
const h = props.width * 0.75 - margin.top - margin.bottom;
const sample = props.sample;
const deriv = props.deriv;
const data1 = props.data;
// Axes min and max
var yMin, yMax, llTheta;
yMax = 1500;
yMin = 1;
llTheta = useMemo(() => logLikSum(sample, props.mu, props.sigma2), [
props.mu,
props.sigma2,
props.sample
]);
const [spring, set] = useSpring(() => ({
xy: [props.mu, props.sigma2],
immediate: false,
config: { duration: 500 }
}));
set({ xy: [props.mu, props.sigma2], immediate: !props.animating });
const bind = useDrag(({ movement: [mx, my], first, memo }) => {
const muStart = first ? props.mu : memo[0];
const sigma2Start = first ? props.sigma2 : memo[1];
const mu = xScale.invert(xScale(muStart) + mx);
const sigma2 = yScale.invert(yScale(sigma2Start) + my);
dispatch({
name: "contourDrag",
value: { mu: props.mu, sigma2: sigma2 }
});
return [muStart, sigma2Start];
});
const xMin = -100;
const xMax = -20;
const hessian = -10 / (2 * props.sigma2 * props.sigma2);
//const y_max = 0.05;
// Create scales
const yScale = scaleLinear()
.domain([yMin, yMax])
.range([h, 0]);
const xScale = scaleLinear()
.domain([xMin, xMax])
.range([0, w]);
// Scales and Axis
const xAxis = axisBottom(xScale).ticks(3);
const yAxis = axisLeft(yScale);
// Line function
const linex = line()
.x(d => xScale(d[1]))
.y(d => yScale(d[0]));
// Update
useEffect(() => {
createChart(durationTime);
}, [props.mu, props.sigma2, w, props.sample]);
const gradientNext = gradientStep(props);
const gradientNextLL = logLikSum(
sample,
props.mu,
gradientNext.points.sigma2
);
// Tooltip
const Tooltip = ({ theta, thetaLab, ll, deriv }) => {
const x = 0;
const y = 0;
const width = 40;
const path = topTooltipPath(width, 100, 10, 10);
const thetaSymb = thetaLab == "mu" ? "mu" : "sigma^2";
const eqLogLik = katex.renderToString(
`\\frac{\\partial}{\\partial \\${thetaSymb}}\\ell = `,
{
displayMode: false,
throwOnError: false
}
);
return (
<g>
<path
d={path}
className="polygonTip"
transform={`translate(${x + margin.left + 5}, ${y +
margin.top}) rotate(90)`}
/>
<foreignObject
x={x + margin.right / 2 + margin.left}
y={y - margin.bottom + 15}
width={100}
height={50}
>
<div className="vizTooltip">
<p>
<span dangerouslySetInnerHTML={{ __html: eqLogLik }} />
{format(".2f")(deriv)}
</p>
</div>
</foreignObject>
</g>
);
};
const createChart = () => {
const node = vizRef.current;
select(node)
.selectAll("g.viz")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x Axis
select(node)
.selectAll("g.xAxis")
.data([0])
.enter()
.append("g")
.attr("class", "xAxis");
select(node)
.select("g.xAxis")
.attr(
"transform",
"translate(" + margin.left + "," + (h + margin.top) + ")"
)
.call(xAxis);
// y Axis
select(node)
.selectAll("g.yAxis")
.data([0])
.enter()
.append("g")
.attr("class", "yAxis");
select(node)
.select("g.yAxis")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(yAxis);
const gViz = select(node)
.selectAll("g.viz")
.data([0])
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x label
gViz
.selectAll(".x-label")
.data([0])
.enter()
.append("text")
.style("text-anchor", "middle")
.attr("class", "x-label MuiTypography-body2");
select(node)
.selectAll(".x-label")
.attr(
"transform",
"translate(" + w / 2 + " ," + (h + margin.bottom - 5) + ")"
)
.text(`ℓ(μ = ${format(".2f")(props.mu)}, σ²)`);
// y label
gViz
.selectAll(".y-label")
.data([0])
.enter()
.append("text")
.style("text-anchor", "middle")
.attr("class", "y-label MuiTypography-body2");
select(node)
.selectAll(".y-label")
.attr("transform", "rotate(-90)")
.attr("text-anchor", "middle")
.attr("x", -(h / 2))
.attr("y", -40)
.text("σ²");
};
const delta = yMax - yMin;
return (
<svg width={props.width} height={h + margin.bottom}>
<g ref={vizRef}>
<g className="viz">
<g clipPath="url(#clipQuadApprox)">
<AnimatedPath
data={data1.data}
x={100}
sigma2={props.sigma2}
xScale={xScale}
yScale={yScale}
linex={linex}
mu={props.mu}
sample={sample}
animating={props.animating}
/>
{props.algo == "newtonRaphson" && (
<NewtonParabola
mu={props.mu}
sigma2={props.sigma2}
yMin={yMin}
yMax={yMax}
xMin={xMin}
xScale={xScale}
yScale={yScale}
linex={linex}
llTheta={llTheta}
deriv={deriv}
hessian={hessian}
count={props.count}
/>
)}
{props.algo == "gradientAscent" && (
<>
<circle
cy={yScale(gradientNext.points.sigma2)}
cx={xScale(gradientNextLL)}
r="5"
className="logLikNewtonX--approx"
/>
<line
className="LogLikNewton--maxima"
x1={xScale(xMin)}
x2={xScale(gradientNextLL)}
y1={yScale(gradientNext.points.sigma2)}
y2={yScale(gradientNext.points.sigma2)}
/>
</>
)}
</g>
</g>
<g clipPath="url(#clipSigma2)">
<animated.g
{...bind()}
transform={spring.xy.interpolate(
(x, y) =>
`translate(${xScale(logLikSum(sample, x, y))}, ${yScale(y)})`
)}
className="draggable"
>
<circle cx={margin.left} cy={0} r="5" className="logLikX" />
<animated.line
className="deriv"
x1={spring.xy.interpolate(
(x, y) =>
margin.left + xScale(xMin - delta * dSigma2(sample, x, y))
)}
x2={spring.xy.interpolate(
(x, y) =>
margin.left + xScale(xMin + delta * dSigma2(sample, x, y))
)}
y1={yScale(yMax - delta)}
y2={yScale(yMax + delta)}
/>
<Tooltip
theta={props.theta}
thetaLab={props.thetaLab}
ll={llTheta}
deriv={deriv}
/>
</animated.g>
</g>
</g>
<defs>
<clipPath id="clipSigma">
<rect id="clip-rect2" x="0" y="-10" width={w} height={h + 10} />
</clipPath>
<clipPath id="clipSigma2">
<rect
id="clip-rect2"
x={margin.left}
y={-10}
width={w + 100}
height={h + 10}
/>
</clipPath>
<clipPath id="clipQuadApprox">
<rect id="clip-rect2" x="0" y="-10" width={h + 100} height={h + 10} />
</clipPath>
</defs>
</svg>
);
}
Example #16
Source File: SamplePlot.js From likelihood with MIT License | 4 votes |
SampleChart = props => {
const vizRef = useRef(null);
// Stuff
const margin = { top: 60, right: 20, bottom: 30, left: 50 };
const durationTime = 200;
const w = props.width - margin.left - margin.right;
const h = props.width * aspect - margin.top - margin.bottom;
const sample = props.sample;
const sigma = Math.sqrt(props.sigma2)
const sigmaTheta = Math.sqrt(props.sigma2Theta)
// x.values
const x_start = props.muTheta - 10 * sigmaTheta;
const x_end = props.muTheta + 10 * sigmaTheta;
const x_start2 = props.mu - 3 * sigma;
const x_end2 = props.mu + 3 * sigma;
const x = range( props.mu - 3 * sigma,
props.mu + 3 * sigma, Math.abs(x_start2 - x_end2) / 50);
x.push(x_end)
x.unshift(x_start)
// Data sets
const data1 = genData(props.mu, sigma, x);
// Axes min and max
const x_max = props.muTheta + sigmaTheta * 5;
const x_min = props.muTheta - sigmaTheta * 5;
//const y_max = max(data1.y);
const y_max = 0.04;
// Create scales
const yScale = scaleLinear()
.domain([0, y_max])
.range([h, 0]);
const xScale = scaleLinear()
.domain([x_min, x_max])
.range([0, w]);
// Scales and Axis
const xAxis = axisBottom(xScale);
const yAxis = axisLeft(yScale);
// Update
useEffect(() => {
createSampleChart(durationTime);
}, [props.mu, props.sigma2, props.highlight, props.sample, w]);
// Tooltip
const Tooltip = ({ d, i }) => {
const x = xScale(d);
const L = normal.pdf(d, props.mu, sigma);
const y = yScale(L);
const path = topTooltipPath(150, 50, 10, 10);
const width = 150;
const eqLogLik = katex.renderToString(`\\ell_{${i + 1}} = `, {
displayMode: false,
throwOnError: false
});
return (
<g>
<path
d={path}
className="polygonTip1"
transform={`translate(${x + margin.left}, ${y + margin.top })`}
/>
<foreignObject
x={x - width / 2 + margin.left}
y={y }
width={width}
height={50}
>
<div className="vizTooltip">
<p>
<span dangerouslySetInnerHTML={{ __html: eqLogLik }} />
log({format(".2n")(L)})
</p>
</div>
</foreignObject>
</g>
);
};
const createSampleChart = () => {
const node = vizRef.current;
// Line function
const linex = line()
.x(d => xScale(d[0]))
.y(d => yScale(d[1]));
select(node)
.selectAll("g.viz")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x Axis
select(node)
.selectAll("g.xAxis")
.data([0])
.enter()
.append("g")
.attr("class", "xAxis");
select(node)
.select("g.xAxis")
.attr(
"transform",
"translate(" + margin.left + "," + (h + margin.top) + ")"
)
.call(xAxis);
// y Axis
select(node)
.selectAll("g.yAxis")
.data([0])
.enter()
.append("g")
.attr("class", "yAxis");
select(node)
.select("g.yAxis")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(yAxis);
const gViz = select(node)
.selectAll("g.viz")
.data([0])
.enter()
.append("g")
.attr("class", "viz")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// x label
gViz
.selectAll(".x-label")
.data([0])
.enter()
.append("text")
.style("text-anchor", "middle")
.attr("class", "x-label");
select(node)
.selectAll(".x-label")
.attr(
"transform",
"translate(" + w / 2 + " ," + (h + margin.bottom) + ")"
)
.text(props.xLabel);
// y label
gViz
.selectAll("#y-label")
.data([0])
.enter()
.append("text")
.style("text-anchor", "middle")
.attr("id", "y-label");
select(node)
.selectAll("#y-label")
.attr("transform", "rotate(-90)")
.attr("text-anchor", "middle")
.attr("x", -(h / 2))
.attr("y", -40)
.text("Density");
// Append dists
// DIST1
gViz
.selectAll("#dist1")
.data([data1.data])
.enter()
.append("svg:path")
.attr("d", linex)
.attr("id", "dist1");
select(node)
.selectAll("#dist1")
.data([data1.data])
.attr("d", linex);
// mu vertical lines
const sampleLines = (mu, id) => {
gViz
.selectAll("#" + id)
.data([0])
.enter()
.append("line")
.attr("class", "logLikLines")
.attr("id", id);
select(node)
.selectAll("#" + id)
.data([0])
.attr("x1", xScale(mu))
.attr("x2", xScale(mu))
.attr("y1", yScale(normal.pdf(mu, props.mu, sigma)))
.attr("y2", yScale(0));
};
//muLines(props.mu0, "mu0");
sample.map((x, i) => sampleLines(x, `sample${i}`));
// Points
gViz
.selectAll("circle")
.data(sample)
.enter()
.append("svg:circle")
.attr("cy", d => yScale(normal.pdf(d, props.mu, sigma)))
.attr("cx", d => xScale(d));
select(node)
.selectAll("circle")
.data(sample)
.attr("cy", d => yScale(normal.pdf(d, props.mu, sigma)))
.attr("cx", d => xScale(d))
.attr("class", "sampleCircles")
.on("mouseover", function(d, i) {
props.setHighlight(i);
select(this).attr("r", 10)
})
.on("mouseout", function() {
props.setHighlight();
select(this).attr("r", 5)
})
.attr("r", (d, i) => {
const r = props.highlight == i ? 10 : 5;
return r;
});
};
return (
<svg width={props.width} height={props.width * aspect}>
<g ref={vizRef} />
{props.highlight >= 0 && (
<Tooltip d={sample[props.highlight]} i={props.highlight} />
)}
</svg>
);
}
Example #17
Source File: bar-chart.js From website with Apache License 2.0 | 4 votes |
BarChart = ({
data,
lineData,
refLineData,
annotations,
handleAnnotationClick,
fill,
lineColor,
marginBottom,
marginLeft,
marginRight,
marginTop,
showTicks,
width,
height,
yMax,
yTicks,
lastXTick,
renderTooltipContents,
perCapLabel,
}) => {
const chartRef = useRef()
const [tooltip, setTooltip] = useState(null)
// Used for tooltip optimization
const [timeoutRef, setTimeoutRef] = useState(null)
const [keyboardFocus, setKeyboardFocus] = useState(false)
// Used when placing annotations
const getValueForDate = date => {
const dateData = data.find(d => d.date.getTime() === date.getTime())
return dateData && dateData.value
}
const totalXMargin = marginLeft + marginRight
const totalYMargin = marginTop + marginBottom
// The x range is over the area in which the chart should be displaying.
// We don't use an X transform to place it in the correct spot, we use range
// instead
const xScale = scaleBand()
.domain(data.map(d => d.date))
.range([marginLeft, width - marginRight])
.padding(0.1)
const dateDomain = extent(data, d => d.date)
// Should probably refactor to use a single x-axis scale
// but the bars make use of the band.
const xScaleTime = scaleTime()
.domain(dateDomain)
.range([marginLeft, width - marginRight])
const yMaxEffective =
yMax || max([...data, ...(refLineData || [])], d => d.value)
const yScale = scaleLinear()
.domain([0, yMaxEffective])
.nice()
.range([height - totalYMargin, 0])
const msInOneMonth = 2628000000
const monthlyTickInterval = Math.ceil(
Math.abs((dateDomain[1] - dateDomain[0]) / (msInOneMonth * 6)),
)
const xTickAmount = timeMonth.every(monthlyTickInterval)
const yTicksThreshold = 4
const yTicksEffective =
yTicks || yMaxEffective < yTicksThreshold ? yMaxEffective : yTicksThreshold
const lastTime = xScaleTime.ticks(timeDay.every(1)).pop()
let lineFn = null
if (lineData) {
lineFn = line()
.defined(d => !Number.isNaN(d.value) && d.value !== null)
.curve(curveCardinal)
.x(d => xScaleTime(d.date))
.y(d => yScale(d.value))
}
const hover = (event, d) => {
// Ensure that tooltip doesn't flash when transitioning between bars
if (timeoutRef) {
clearTimeout(timeoutRef)
}
const isTouchEvent = !event.clientX
const eventX = isTouchEvent ? event.touches[0].clientX : event.clientX
const eventY = isTouchEvent ? event.touches[0].clientY : event.clientY
setTooltip({
top: isTouchEvent ? eventY - 130 : eventY + 10,
left: isTouchEvent ? eventX - 80 : eventX + 5,
d,
})
}
const mouseOut = () => {
if (timeoutRef) {
clearTimeout(timeoutRef)
}
setTimeoutRef(setTimeout(() => setTooltip(null), 200))
}
useEffect(() => {
if (keyboardFocus === false || typeof data[keyboardFocus] === 'undefined') {
return
}
const column = data[keyboardFocus]
setTooltip({
top: chartRef.current.getBoundingClientRect().top,
left: chartRef.current.getBoundingClientRect().left,
d: column,
})
}, [keyboardFocus])
return (
<>
<svg
className={classnames(chartStyles.chart, styles.chart)}
viewBox={`0 0 ${width} ${height}`}
tabIndex="0"
aria-hidden
ref={chartRef}
onBlur={() => {
setTooltip(null)
setKeyboardFocus(false)
}}
onKeyDown={event => {
if (event.key === 'Escape') {
setTooltip(null)
setKeyboardFocus(false)
chartRef.current.blur()
}
if (event.key === 'ArrowRight') {
setKeyboardFocus(
keyboardFocus < data.length ? keyboardFocus + 1 : data.length,
)
}
if (
(event.shiftKey && event.key === 'Tab') ||
event.key === 'ArrowLeft'
) {
setKeyboardFocus(keyboardFocus > 0 ? keyboardFocus - 1 : 0)
}
}}
>
<g transform={`translate(${marginLeft} ${marginTop})`}>
<text className={classnames(chartStyles.label, styles.directions)}>
Use arrows to move, Escape to leave.
</text>
</g>
{/* y ticks */}
<g transform={`translate(${marginLeft} ${marginTop})`}>
{yScale.ticks(yTicksEffective).map(
(tick, i) =>
i < showTicks && (
<g key={tick}>
{/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
<svg
y={yScale(tick) + 6}
x="-10"
className={chartStyles.yTickLabel}
>
<text className={chartStyles.label}>
{formatNumber(tick)}
{tick > 0 &&
perCapLabel /* this only displays if passed */}
</text>
</svg>
<line
className={chartStyles.gridLine}
x1={0}
x2={width - totalXMargin}
y1={yScale(tick)}
y2={yScale(tick)}
/>
</g>
),
)}
</g>
{/* x ticks (dates) */}
<g transform={`translate(0, ${height - marginBottom})`}>
{xScaleTime.ticks(xTickAmount).map(d => (
<Fragment key={`x-${d}`}>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
key={d}
x={xScaleTime(d)}
y="20"
>{`${formatDate(d)}`}</text>
<line
className={chartStyles.label}
stroke={colors.colorSlate500}
x1={xScaleTime(d)}
y1="0"
x2={xScaleTime(d)}
y2="5"
/>
</Fragment>
))}
{lastXTick && (
<>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
x={xScaleTime(lastTime)}
y="20"
>{`${formatDate(lastTime)}`}</text>
<line
className={chartStyles.label}
stroke={colors.colorSlate500}
x1={xScaleTime(lastTime)}
y1="0"
x2={xScaleTime(lastTime)}
y2="5"
/>
</>
)}
</g>
<mask id="dataMask">
<rect
x="0"
y="0"
width={width - marginRight}
height={height - totalYMargin}
fill="white"
/>
</mask>
{/* data */}
<g transform={`translate(0 ${marginTop})`} mask="url(#dataMask)">
{/* bars (data) */}
{data.map((d, key) => (
<rect
key={d.date + d.value}
x={xScale(d.date)}
y={yScale(d.value)}
height={yScale(0) - yScale(d.value)}
width={xScale.bandwidth()}
fillOpacity={lineData ? 1 : 0.8}
fill={fill}
className={classnames(
renderTooltipContents && styles.interactiveBar,
key === keyboardFocus && styles.selected,
)}
onMouseOver={event => hover(event, d)}
onFocus={event => hover(event, d)}
onMouseOut={mouseOut}
onBlur={mouseOut}
/>
))}
{/* line */}
{lineData && (
<path
d={lineFn(lineData)}
stroke={lineColor}
strokeWidth="3"
fill="none"
/>
)}
{/* reference line */}
{refLineData && (
<path
d={lineFn(refLineData)}
stroke="black"
strokeWidth="2"
strokeDasharray="4"
fill="none"
/>
)}
</g>
{/* annotations */}
{annotations && (
<g transform={`translate(0 ${marginTop})`}>
{annotations
.filter(
annotation =>
xScaleTime(annotation.date) >= xScaleTime(dateDomain[0]) &&
xScaleTime(annotation.date) <= xScaleTime(dateDomain[1]),
)
.map(d => (
<AnnotationBubble
content={d}
xScaleTime={xScaleTime}
yScale={yScale}
handleAnnotationClick={handleAnnotationClick}
getValueForDate={getValueForDate}
/>
))}
</g>
)}
</svg>
{renderTooltipContents && tooltip && (
<Tooltip {...tooltip}>{renderTooltipContents(tooltip.d)} </Tooltip>
)}
</>
)
}
Example #18
Source File: ContourLogLik.js From likelihood with MIT License | 4 votes |
ContourChart = props => {
const vizRef = useRef(null);
const dispatch = useContext(VizDispatch);
// Stuff
const margin = { top: 0, right: 20, bottom: 40, left: 50 };
const w = props.width - margin.left - margin.right;
const h = props.width * 0.75 - margin.top - margin.bottom;
const sample = props.sample;
const sigmaTheta = Math.sqrt(props.sigma2Theta);
const muMax = props.muTheta + sigmaTheta * 5;
const muMin = props.muTheta - sigmaTheta * 5;
const sigma2MLE = props.sigma2Theta;
const sigma2Max = 1500;
const sigma2Min = 1;
// For gradient ascent illustration
const [spring, set] = useSpring(() => ({
xy: [props.mu, props.sigma2],
immediate: false,
config: { duration: 500 }
}));
const bind = useDrag(({ movement: [mx, my], first, memo }) => {
const muStart = first ? props.mu : memo[0];
const sigma2Start = first ? props.sigma2 : memo[1];
const mu = xScale.invert(xScale(muStart) + mx);
const sigma2 = yScale.invert(yScale(sigma2Start) + my);
dispatch({
name: "contourDrag",
value: { mu: mu, sigma2: sigma2 }
});
return [muStart, sigma2Start];
});
const iterate = () => {
dispatch({
name: "algoIterate",
value: {
increment: 1,
}
});
};
useInterval(() => {
iterate();
}, props.algoDelay);
set({ xy: [props.mu, props.sigma2], immediate: !props.animating });
const llMin = -300;
const llMax = -20;
const thresholds = useMemo(
() => range(llMin, llMax, (llMax - llMin) / 100),
[]
);
const yScale = scaleLinear([sigma2Min, sigma2Max], [h, 0]);
const xScale = scaleLinear([muMin, muMax], [0, w]);
const linex = useMemo(
() =>
line()
.x(d => xScale(d.mu))
.y(d => yScale(d.sigma2)),
[w]
);
const grid = useMemo(
() => createGrid(muMin, muMax, sigma2Min, sigma2Max, sample),
[props.sample]
);
const color = useMemo(
() =>
scaleLinear()
.domain([-100, max(grid)])
.range(["#82b3aa", "#fff"])
.interpolate(interpolateRgb.gamma(0.6)),
[props.sample]
);
const contour = useMemo(
() =>
contours()
.size([grid.n, grid.m])
.thresholds(thresholds)(grid)
.map(({ type, value, coordinates }) => {
return {
type,
value,
coordinates: coordinates.map(rings => {
return rings.map(points => {
return points.map(([mu, sigma2]) => [
xScale(muMin + mu * grid.muStep),
yScale(sigma2Min + sigma2 * grid.sigmaStep)
]);
});
})
};
}),
[props.sample, w]
);
const contourPaths = useMemo(
() =>
contour.map((d, i) => {
return (
<path
d={geoPath()(d)}
className="contour"
fill={color(d.value)}
fillOpacity={1}
stroke="#485460"
strokeWidth={i % 5 ? 0.5 : 1.5}
strokeOpacity={0.75}
strokeLinejoin="round"
key={i}
/>
);
}),
[props.sample, w]
);
const ll = useMemo(
() => format(".2f")(logLikSum(sample, props.mu, props.sigma2)),
[sample, props.mu, props.sigma2]
);
return (
<svg width={props.width} height={h + margin.bottom}>
<g ref={vizRef}>
<g
className="viz"
transform={"translate(" + margin.left + "," + 0 + ")"}
>
{contourPaths}
<animated.line
x1={xScale(muMin)}
x2={xScale(muMax)}
y1={0}
y2={0}
className="LogLikMu"
transform={spring.xy.interpolate(
(x, y) => `translate(0, ${yScale(y)})`
)}
/>
<animated.line
y1={yScale(sigma2Min)}
y2={yScale(sigma2Max)}
x1={0}
x2={0}
transform={spring.xy.interpolate(
(x, y) => `translate(${xScale(x)}, 0)`
)}
className="LogLikSigma"
/>
<animated.g
{...bind()}
transform={spring.xy.interpolate(
(x, y) => `translate(${xScale(x)}, ${yScale(y)})`
)}
className="draggable"
>
<circle cx={0} cy={0} r="5" className="logLikX" />
<Tooltip x={0} y={0} equation={eqLogLik(ll)} margin={margin} />
</animated.g>
<path d={linex(props.drawGradientPath)} className="gradientDescent" />
<rect
id="clip-rect"
x="0"
y="0"
width={w}
height={h}
fill="none"
stroke="#fff"
strokeWidth="3px"
/>
</g>
</g>
</svg>
);
}
Example #19
Source File: Timeseries.js From covid19india-react with MIT License | 4 votes |
function Timeseries({
statistics,
timeseries,
dates,
endDate,
chartType,
isUniform,
isLog,
isMovingAverage,
noRegionHighlightedDistrictData,
}) {
const {t} = useTranslation();
const refs = useRef([]);
const [wrapperRef, {width, height}] = useMeasure();
const [highlightedDate, setHighlightedDate] = useState(
dates[dates.length - 1]
);
useEffect(() => {
setHighlightedDate(dates[dates.length - 1]);
}, [dates]);
const getTimeseriesStatistic = useCallback(
(date, statistic, movingAverage = isMovingAverage) => {
return getStatistic(timeseries?.[date], chartType, statistic, {
movingAverage,
});
},
[chartType, isMovingAverage, timeseries]
);
const condenseChart = useMemo(() => {
const T = dates.length;
const days = differenceInDays(
parseIndiaDate(dates[T - 1]),
parseIndiaDate(dates[0])
);
// Chart extremes
const chartRight = width - margin.right;
// Bar widths
const axisWidth = Math.max(0, chartRight - margin.left) / (1.25 * days);
return axisWidth < 4;
}, [width, dates]);
const xScale = useMemo(() => {
const T = dates.length;
const chartRight = width - margin.right;
return scaleTime()
.clamp(true)
.domain([
parseIndiaDate(dates[0] || endDate),
parseIndiaDate(dates[T - 1] || endDate),
])
.range([margin.left, chartRight]);
}, [width, endDate, dates]);
const yScales = useMemo(() => {
const chartBottom = height - margin.bottom;
const addScaleBuffer = (scale, log = false) => {
const domain = scale.domain();
if (log) {
scale.domain([
domain[0],
domain[0] * Math.pow(domain[1] / domain[0], 1 / yScaleShrinkFactor),
]);
} else {
scale.domain([
domain[0],
domain[0] + (domain[1] - domain[0]) / yScaleShrinkFactor,
]);
}
return scale;
};
const [uniformScaleMin, uniformScaleMax] = extent(
dates.reduce((res, date) => {
res.push(
...PRIMARY_STATISTICS.map((statistic) =>
getTimeseriesStatistic(date, statistic)
)
);
return res;
}, [])
);
const yScaleUniformLinear = addScaleBuffer(
scaleLinear()
.clamp(true)
.domain([Math.min(0, uniformScaleMin), Math.max(1, uniformScaleMax)])
.range([chartBottom, margin.top])
.nice(4)
);
const yScaleUniformLog = addScaleBuffer(
scaleLog()
.clamp(true)
.domain([1, Math.max(10, uniformScaleMax)])
.range([chartBottom, margin.top])
.nice(4),
true
);
return statistics.map((statistic) => {
if (isUniform) {
if (
chartType === 'total' &&
isLog &&
PRIMARY_STATISTICS.includes(statistic)
) {
return yScaleUniformLog;
} else if (PRIMARY_STATISTICS.includes(statistic)) {
return yScaleUniformLinear;
}
}
const [scaleMin, scaleMax] = extent(dates, (date) =>
getTimeseriesStatistic(date, statistic)
);
if (chartType === 'total' && isLog) {
return addScaleBuffer(
scaleLog()
.clamp(true)
.domain([1, Math.max(10, scaleMax)])
.range([chartBottom, margin.top])
.nice(4),
true
);
} else {
return addScaleBuffer(
scaleLinear()
.clamp(true)
.domain([
Math.min(0, scaleMin),
STATISTIC_CONFIGS[statistic].format === '%'
? Math.min(100, Math.max(1, scaleMax))
: Math.max(1, scaleMax),
])
.range([chartBottom, margin.top])
.nice(4)
);
}
});
}, [
height,
chartType,
isUniform,
isLog,
statistics,
dates,
getTimeseriesStatistic,
]);
useEffect(() => {
if (!width || !height) return;
const T = dates.length;
// Chart extremes
const chartRight = width - margin.right;
const chartBottom = height - margin.bottom;
const isDiscrete = chartType === 'delta' && !isMovingAverage;
const xAxis = (g) =>
g
.attr('class', 'x-axis')
.call(axisBottom(xScale).ticks(numTicksX(width)));
const xAxis2 = (g, yScale) => {
g.attr('class', 'x-axis2')
.call(axisBottom(xScale).tickValues([]).tickSize(0))
.select('.domain')
.style('transform', `translate3d(0, ${yScale(0)}px, 0)`);
if (yScale(0) !== chartBottom) g.select('.domain').attr('opacity', 0.4);
else g.select('.domain').attr('opacity', 0);
};
const yAxis = (g, yScale, format) =>
g.attr('class', 'y-axis').call(
axisRight(yScale)
.ticks(4)
.tickFormat((num) => formatNumber(num, format))
.tickPadding(4)
);
function mousemove(event) {
const xm = pointer(event)[0];
const date = xScale.invert(xm);
if (!isNaN(date)) {
const bisectDate = bisector((date) => parseIndiaDate(date)).left;
const index = bisectDate(dates, date, 1);
const dateLeft = dates[index - 1];
const dateRight = dates[index];
setHighlightedDate(
date - parseIndiaDate(dateLeft) < parseIndiaDate(dateRight) - date
? dateLeft
: dateRight
);
}
}
function mouseout(event) {
setHighlightedDate(dates[T - 1]);
}
/* Begin drawing charts */
statistics.forEach((statistic, i) => {
const ref = refs.current[i];
const svg = select(ref);
const t = svg.transition().duration(D3_TRANSITION_DURATION);
const yScale = yScales[i];
const statisticConfig = STATISTIC_CONFIGS[statistic];
const format =
STATISTIC_CONFIGS[statistic].format === '%' ? '%' : 'short';
const isNonLinear = !!STATISTIC_CONFIGS[statistic]?.nonLinear;
/* X axis */
svg
.select('.x-axis')
.style('transform', `translate3d(0, ${chartBottom}px, 0)`)
.transition(t)
.call(xAxis);
svg.select('.x-axis2').transition(t).call(xAxis2, yScale);
/* Y axis */
svg
.select('.y-axis')
.style('transform', `translate3d(${chartRight}px, 0, 0)`)
.transition(t)
.call(yAxis, yScale, format);
/* Path dots */
svg
.selectAll('circle.normal')
.data(condenseChart ? [] : dates, (date) => date)
.join((enter) =>
enter
.append('circle')
.attr('class', 'normal')
.attr('fill', statisticConfig?.color)
.attr('stroke', statisticConfig?.color)
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) =>
yScale(isDiscrete ? 0 : getTimeseriesStatistic(date, statistic))
)
.attr('r', 2)
)
.transition(t)
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) => yScale(getTimeseriesStatistic(date, statistic)));
const areaPath = (dates, allZero = false) =>
area()
.curve(curveStep)
.x((date) => xScale(parseIndiaDate(date)))
.y0(yScale(0))
.y1(
allZero
? yScale(0)
: (date) => yScale(getTimeseriesStatistic(date, statistic, false))
)(dates);
svg
.selectAll('.trend-area')
.data(
T && chartType === 'delta' && !isNonLinear && condenseChart
? [dates]
: []
)
.join(
(enter) =>
enter
.append('path')
.attr('class', 'trend-area')
.call((enter) =>
enter
.attr('d', (dates) => areaPath(dates, true))
.transition(t)
.attr('d', areaPath)
),
(update) =>
update.call((update) =>
update.transition(t).attrTween('d', function (dates) {
const previous = select(this).attr('d');
const current = areaPath(dates);
return interpolatePath(previous, current);
})
),
(exit) =>
exit.call((exit) =>
exit
.transition(t)
.attr('d', (dates) => areaPath(dates, true))
.remove()
)
)
.transition(t)
.attr('opacity', isMovingAverage ? 0.3 : 1);
svg
.selectAll('.stem')
.data(
T && chartType === 'delta' && !isNonLinear && !condenseChart
? dates
: [],
(date) => date
)
.join(
(enter) =>
enter
.append('line')
.attr('class', 'stem')
.attr('stroke-width', 4)
.attr('x1', (date) => xScale(parseIndiaDate(date)))
.attr('y1', yScale(0))
.attr('x2', (date) => xScale(parseIndiaDate(date)))
.attr('y2', yScale(0)),
(update) => update,
(exit) =>
exit.call((exit) =>
exit
.transition(t)
.attr('x1', (date) => xScale(parseIndiaDate(date)))
.attr('x2', (date) => xScale(parseIndiaDate(date)))
.attr('y1', yScale(0))
.attr('y2', yScale(0))
.remove()
)
)
.transition(t)
.attr('x1', (date) => xScale(parseIndiaDate(date)))
.attr('y1', yScale(0))
.attr('x2', (date) => xScale(parseIndiaDate(date)))
.attr('y2', (date) =>
yScale(getTimeseriesStatistic(date, statistic, false))
)
.attr('opacity', isMovingAverage ? 0.2 : 1);
const linePath = (
movingAverage = isMovingAverage,
curve = curveMonotoneX
) =>
line()
.curve(curve)
.x((date) => xScale(parseIndiaDate(date)))
.y((date) =>
yScale(getTimeseriesStatistic(date, statistic, movingAverage))
);
svg
.select('.trend')
.selectAll('path')
.data(
T && (chartType === 'total' || isNonLinear || isMovingAverage)
? [dates]
: []
)
.join(
(enter) =>
enter
.append('path')
.attr('class', 'trend')
.attr('fill', 'none')
.attr('stroke-width', 4)
.attr('d', linePath())
.call((enter) => enter.transition(t).attr('opacity', 1)),
(update) =>
update.call((update) =>
update
.transition(t)
.attrTween('d', function (date) {
const previous = select(this).attr('d');
const current = linePath()(date);
return interpolatePath(previous, current);
})
.attr('opacity', 1)
),
(exit) =>
exit.call((exit) => exit.transition(t).attr('opacity', 0).remove())
)
.attr('stroke', statisticConfig?.color + (condenseChart ? '99' : '50'));
svg
.select('.trend-background')
.selectAll('path')
.data(
T && chartType === 'delta' && isNonLinear && isMovingAverage
? [dates]
: []
)
.join(
(enter) =>
enter
.append('path')
.attr('class', 'trend-background')
.attr('fill', 'none')
.attr('stroke-width', 4)
.attr('d', linePath(false, curveLinear))
.call((enter) => enter.transition(t).attr('opacity', 0.2)),
(update) =>
update.call((update) =>
update
.transition(t)
.attrTween('d', function (date) {
const previous = select(this).attr('d');
const current = linePath(false, curveLinear)(date);
return interpolatePath(previous, current);
})
.attr('opacity', 0.2)
),
(exit) =>
exit.call((exit) => exit.transition(t).attr('opacity', 0).remove())
)
.attr('stroke', statisticConfig?.color + (condenseChart ? '99' : '50'));
svg.selectAll('*').attr('pointer-events', 'none');
svg
.on('mousemove', mousemove)
.on('touchmove', (event) => mousemove(event.touches[0]))
.on('mouseout touchend', mouseout);
});
}, [
width,
height,
chartType,
isMovingAverage,
condenseChart,
xScale,
yScales,
statistics,
getTimeseriesStatistic,
dates,
]);
useEffect(() => {
statistics.forEach((statistic, i) => {
const ref = refs.current[i];
const svg = select(ref);
const statisticConfig = STATISTIC_CONFIGS[statistic];
const yScale = yScales[i];
const t = svg.transition().duration(D3_TRANSITION_DURATION);
svg
.selectAll('circle.condensed')
.data(
condenseChart && highlightedDate ? [highlightedDate] : [],
(date) => date
)
.join((enter) =>
enter
.append('circle')
.attr('class', 'condensed')
.attr('fill', statisticConfig?.color)
.attr('stroke', statisticConfig?.color)
.attr('pointer-events', 'none')
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) =>
yScale(getTimeseriesStatistic(date, statistic))
)
.attr('r', 4)
)
.transition(t)
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) => yScale(getTimeseriesStatistic(date, statistic)));
if (!condenseChart) {
svg
.selectAll('circle')
.attr('r', (date) => (date === highlightedDate ? 4 : 2));
}
});
}, [
condenseChart,
highlightedDate,
xScale,
yScales,
statistics,
getTimeseriesStatistic,
]);
const getTimeseriesStatisticDelta = useCallback(
(statistic) => {
if (!highlightedDate) return;
const currCount = getTimeseriesStatistic(highlightedDate, statistic);
if (STATISTIC_CONFIGS[statistic]?.hideZero && currCount === 0) return;
const prevDate =
dates[dates.findIndex((date) => date === highlightedDate) - 1];
const prevCount = getTimeseriesStatistic(prevDate, statistic);
return currCount - prevCount;
},
[dates, highlightedDate, getTimeseriesStatistic]
);
const trail = useMemo(
() =>
statistics.map((statistic, index) => ({
animationDelay: `${index * 250}ms`,
})),
[statistics]
);
return (
<div className="Timeseries">
{statistics.map((statistic, index) => {
const total = getTimeseriesStatistic(highlightedDate, statistic);
const delta = getTimeseriesStatisticDelta(statistic, index);
const statisticConfig = STATISTIC_CONFIGS[statistic];
return (
<div
key={statistic}
className={classnames('svg-parent fadeInUp', `is-${statistic}`)}
style={trail[index]}
ref={index === 0 ? wrapperRef : null}
>
{highlightedDate && (
<div className={classnames('stats', `is-${statistic}`)}>
<h5 className="title">
{t(capitalize(statisticConfig.displayName))}
</h5>
<h5>{formatDate(highlightedDate, 'dd MMMM')}</h5>
<div className="stats-bottom">
<h2>
{!noRegionHighlightedDistrictData ||
!statisticConfig?.hasPrimary
? formatNumber(
total,
statisticConfig.format !== 'short'
? statisticConfig.format
: 'long',
statistic
)
: '-'}
</h2>
<h6>
{!noRegionHighlightedDistrictData ||
!statisticConfig?.hasPrimary
? `${delta > 0 ? '+' : ''}${formatNumber(
delta,
statisticConfig.format !== 'short'
? statisticConfig.format
: 'long',
statistic
)}`
: ''}
</h6>
</div>
</div>
)}
<svg
ref={(element) => {
refs.current[index] = element;
}}
preserveAspectRatio="xMidYMid meet"
>
<g className="x-axis" />
<g className="x-axis2" />
<g className="y-axis" />
<g className="trend-background" />
<g className="trend" />
</svg>
</div>
);
})}
</div>
);
}
Example #20
Source File: Minigraphs.js From covid19india-react with MIT License | 4 votes |
function Minigraphs({timeseries, date: timelineDate}) {
const refs = useRef([]);
const endDate = timelineDate || getIndiaDateYesterdayISO();
let [wrapperRef, {width}] = useMeasure();
width = Math.min(width, maxWidth);
const dates = useMemo(() => {
const pastDates = Object.keys(timeseries || {}).filter(
(date) => date <= endDate
);
const lastDate = pastDates[pastDates.length - 1];
const cutOffDateLower = formatISO(
subDays(parseIndiaDate(lastDate), MINIGRAPH_LOOKBACK_DAYS),
{representation: 'date'}
);
return pastDates.filter((date) => date >= cutOffDateLower);
}, [endDate, timeseries]);
const getMinigraphStatistic = useCallback(
(date, statistic) => {
return getStatistic(timeseries?.[date], 'delta', statistic);
},
[timeseries]
);
useEffect(() => {
if (!width) return;
const T = dates.length;
const chartRight = width - margin.right;
const chartBottom = height - margin.bottom;
const xScale = scaleTime()
.clamp(true)
.domain([
parseIndiaDate(dates[0] || endDate),
parseIndiaDate(dates[T - 1]) || endDate,
])
.range([margin.left, chartRight]);
refs.current.forEach((ref, index) => {
const svg = select(ref);
const statistic = LEVEL_STATISTICS[index];
const color = STATISTIC_CONFIGS[statistic].color;
const dailyMaxAbs = max(dates, (date) =>
Math.abs(getMinigraphStatistic(date, statistic))
);
const yScale = scaleLinear()
.clamp(true)
.domain([-dailyMaxAbs, dailyMaxAbs])
.range([chartBottom, margin.top]);
const linePath = line()
.curve(curveMonotoneX)
.x((date) => xScale(parseIndiaDate(date)))
.y((date) => yScale(getMinigraphStatistic(date, statistic)));
let pathLength;
svg
.selectAll('path')
.data(T ? [dates] : [])
.join(
(enter) =>
enter
.append('path')
.attr('fill', 'none')
.attr('stroke', color + '99')
.attr('stroke-width', 2.5)
.attr('d', linePath)
.attr('stroke-dasharray', function () {
return (pathLength = this.getTotalLength());
})
.call((enter) =>
enter
.attr('stroke-dashoffset', pathLength)
.transition()
.delay(100)
.duration(2500)
.attr('stroke-dashoffset', 0)
),
(update) =>
update
.attr('stroke-dasharray', null)
.transition()
.duration(500)
.attrTween('d', function (date) {
const previous = select(this).attr('d');
const current = linePath(date);
return interpolatePath(previous, current);
})
.selection()
);
svg
.selectAll('circle')
.data(T ? [dates[T - 1]] : [])
.join(
(enter) =>
enter
.append('circle')
.attr('fill', color)
.attr('r', 2.5)
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) =>
yScale(getMinigraphStatistic(date, statistic))
)
.style('opacity', 0)
.call((enter) =>
enter
.transition()
.delay(2100)
.duration(500)
.style('opacity', 1)
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) =>
yScale(getMinigraphStatistic(date, statistic))
)
),
(update) =>
update
.transition()
.duration(500)
.attr('cx', (date) => xScale(parseIndiaDate(date)))
.attr('cy', (date) =>
yScale(getMinigraphStatistic(date, statistic))
)
.style('opacity', 1)
.selection()
);
});
}, [endDate, dates, width, getMinigraphStatistic]);
return (
<div className="Minigraph">
{LEVEL_STATISTICS.map((statistic, index) => (
<div
key={statistic}
className={classnames('svg-parent')}
ref={index === 0 ? wrapperRef : null}
style={{width: `calc(${100 / LEVEL_STATISTICS.length}%)`}}
>
<svg
ref={(el) => {
refs.current[index] = el;
}}
preserveAspectRatio="xMidYMid meet"
width={width}
height={height}
/>
</div>
))}
</div>
);
}
Example #21
Source File: multi-line-chart.js From website with Apache License 2.0 | 4 votes |
MultiLineChart = ({
data,
marginBottom,
marginLeft,
marginRight,
marginTop = 0,
showTicks,
width,
height,
yMax,
yTicks,
lastXTick,
perCapLabel,
}) => {
const totalXMargin = marginLeft + marginRight
const totalYMargin = marginTop + marginBottom
const dates = []
const values = []
data.forEach(item => {
Object.keys(item.data).forEach(category => {
item.data[category].forEach(row => {
dates.push(row.date)
values.push(row.value)
})
})
})
const dateDomain = extent(dates)
const xScaleTime = scaleTime()
.domain(dateDomain)
.range([marginLeft, width - marginRight])
const yMaxEffective = yMax || max(values)
const yScale = scaleLinear()
.domain([0, yMaxEffective])
.nice()
.range([height - totalYMargin, 0])
const msInOneMonth = 2628000000
const monthlyTickInterval = Math.ceil(
Math.abs((dateDomain[1] - dateDomain[0]) / (msInOneMonth * 6)),
)
const xTickAmount = timeMonth.every(monthlyTickInterval)
const yTicksThreshold = 4
const yTicksEffective =
yTicks || yMaxEffective < yTicksThreshold ? yMaxEffective : yTicksThreshold
const lastTime = xScaleTime.ticks(timeDay.every(1)).pop()
const lineFn = line()
.defined(d => !Number.isNaN(d.value) && d.value !== null)
.curve(curveCardinal)
.x(d => xScaleTime(d.date))
.y(d => yScale(d.value))
// TODO make this dry-er w/r/t the single line chart component
return (
<>
<svg
className={chartStyles.chart}
viewBox={`0 0 ${width} ${height}`}
aria-hidden
>
{/* y ticks */}
<g transform={`translate(${marginLeft} ${marginTop})`}>
{yScale.ticks(yTicksEffective).map(
(tick, i) =>
i < showTicks && (
<g key={tick}>
{/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
<svg
y={yScale(tick) + 6}
x="-10"
className={chartStyles.yTickLabel}
>
<text className={chartStyles.label}>
{formatNumber(tick)}
{tick > 0 &&
perCapLabel /* this only displays if passed */}
</text>
</svg>
<line
className={chartStyles.gridLine}
x1={0}
x2={width - totalXMargin}
y1={yScale(tick)}
y2={yScale(tick)}
/>
</g>
),
)}
</g>
{/* x ticks (dates) */}
<g transform={`translate(0, ${height - marginBottom})`}>
{xScaleTime.ticks(xTickAmount).map(d => (
<Fragment key={`x-${d}`}>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
key={d}
x={xScaleTime(d)}
y="20"
>{`${formatDate(d)}`}</text>
<line
className={chartStyles.label}
stroke={colors.colorSlate500}
x1={xScaleTime(d)}
y1="0"
x2={xScaleTime(d)}
y2="5"
/>
</Fragment>
))}
{lastXTick && (
<>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
x={xScaleTime(lastTime)}
y="20"
>{`${formatDate(lastTime)}`}</text>
<line
className={chartStyles.label}
stroke={colors.colorSlate500}
x1={xScaleTime(lastTime)}
y1="0"
x2={xScaleTime(lastTime)}
y2="5"
/>
</>
)}
</g>
<mask id="dataMask">
<rect
x="0"
y="0"
width={width - marginRight}
height={height - totalYMargin}
fill="white"
/>
</mask>
{/* data */}
<g transform={`translate(0 ${marginTop})`} mask="url(#dataMask)">
{data && (
<>
{data.map(item => (
<>
{Object.keys(item.data).map(category => (
<path
d={lineFn(item.data[category])}
stroke={item.colorMap[category]}
strokeWidth="1px"
fill="none"
/>
))}
</>
))}
</>
)}
</g>
</svg>
</>
)
}
Example #22
Source File: line-chart.js From website with Apache License 2.0 | 4 votes |
LineChart = ({
data,
marginBottom,
marginLeft,
marginRight,
marginTop = 0,
showTicks,
width,
height,
yMax,
yTicks,
lastXTick,
perCapLabel,
renderTooltipContents,
}) => {
const totalXMargin = marginLeft + marginRight
const totalYMargin = marginTop + marginBottom
const dates = []
const values = []
data.forEach(item => {
item.data.forEach(row => {
dates.push(row.date)
values.push(row.value)
})
})
const [tooltip, setTooltip] = useState(null)
const [timeoutRef, setTimeoutRef] = useState(null)
const hover = (event, dataLine) => {
// Ensure that tooltip doesn't flash when transitioning between bars
if (timeoutRef) {
clearTimeout(timeoutRef)
}
const isTouchEvent = !event.clientX
const eventX = isTouchEvent ? event.touches[0].clientX : event.clientX
const eventY = isTouchEvent ? event.touches[0].clientY : event.clientY
setTooltip({
top: isTouchEvent ? eventY - 130 : eventY + 10,
left: isTouchEvent ? eventX - 80 : eventX + 5,
d: dataLine,
})
}
const mouseOut = () => {
if (timeoutRef) {
clearTimeout(timeoutRef)
}
setTimeoutRef(setTimeout(() => setTooltip(null), 200))
}
const dateDomain = extent(dates)
const xScaleTime = scaleTime()
.domain(dateDomain)
.range([marginLeft, width - marginRight])
const yMaxEffective = yMax || max(values)
const yScale = scaleLinear()
.domain([0, yMaxEffective])
.nice()
.range([height - totalYMargin, 0])
const msInOneMonth = 2628000000
const monthlyTickInterval = Math.ceil(
Math.abs((dateDomain[1] - dateDomain[0]) / (msInOneMonth * 6)),
)
const xTickAmount = timeMonth.every(monthlyTickInterval)
const yTicksThreshold = 4
const yTicksEffective =
yTicks || yMaxEffective < yTicksThreshold ? yMaxEffective : yTicksThreshold
const lastTime = xScaleTime.ticks(timeDay.every(1)).pop()
const lineFn = line()
.defined(d => !Number.isNaN(d.value) && d.value !== null)
.curve(curveCardinal)
.x(d => xScaleTime(d.date))
.y(d => yScale(d.value))
return (
<>
<svg
className={chartStyles.chart}
viewBox={`0 0 ${width} ${height}`}
aria-hidden
>
{/* y ticks */}
<g transform={`translate(${marginLeft} ${marginTop})`}>
{yScale.ticks(yTicksEffective).map(
(tick, i) =>
i < showTicks && (
<g key={tick}>
{/* Do not remove nested svg. See https://github.com/COVID19Tracking/website/pull/645#discussion_r411676987 */}
<svg
y={yScale(tick) + 6}
x="-10"
className={chartStyles.yTickLabel}
>
<text className={chartStyles.label}>
{formatNumber(tick)}
{tick > 0 &&
perCapLabel /* this only displays if passed */}
</text>
</svg>
<line
className={chartStyles.gridLine}
x1={0}
x2={width - totalXMargin}
y1={yScale(tick)}
y2={yScale(tick)}
/>
</g>
),
)}
</g>
{/* x ticks (dates) */}
<g transform={`translate(0, ${height - marginBottom})`}>
{xScaleTime.ticks(xTickAmount).map(d => (
<Fragment key={`x-${d}`}>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
key={d}
x={xScaleTime(d)}
y="20"
>{`${formatDate(d)}`}</text>
<line
className={chartStyles.label}
stroke={colors.colorSlate500}
x1={xScaleTime(d)}
y1="0"
x2={xScaleTime(d)}
y2="5"
/>
</Fragment>
))}
{lastXTick && (
<>
<text
className={`${chartStyles.label} ${chartStyles.xTickLabel}`}
x={xScaleTime(lastTime)}
y="20"
>{`${formatDate(lastTime)}`}</text>
<line
className={chartStyles.label}
stroke={colors.colorSlate500}
x1={xScaleTime(lastTime)}
y1="0"
x2={xScaleTime(lastTime)}
y2="5"
/>
</>
)}
</g>
<mask id="dataMask">
<rect
x="0"
y="0"
width={width - marginRight}
height={height - totalYMargin}
fill="white"
/>
</mask>
{/* data */}
<g transform={`translate(0 ${marginTop})`} mask="url(#dataMask)">
{data && (
<>
{data.map(dataLine => (
<>
<path
d={lineFn(dataLine.data)}
stroke={dataLine.color}
strokeWidth={dataLine.stroke}
fill="none"
/>
{/* Add a wider hidden path for tooltips. */}
<path
d={lineFn(dataLine.data)}
stroke="transparent"
strokeWidth={6}
fill="none"
onMouseOver={event => hover(event, dataLine)}
onFocus={event => hover(event, dataLine)}
onMouseOut={mouseOut}
onBlur={mouseOut}
/>
</>
))}
</>
)}
</g>
</svg>
{renderTooltipContents && tooltip && (
<Tooltip {...tooltip}>{renderTooltipContents(tooltip.d)} </Tooltip>
)}
</>
)
}