bindings/jupyter-modules/jupyter-ma-causal/src/components/line-chart/index.js (249 lines of code) (raw):

import React, {Component} from 'react'; import PropTypes from 'prop-types'; import {line as d3Line} from 'd3-shape'; import {scaleLinear} from 'd3-scale'; import {format as d3Format} from 'd3-format'; export default class Chart extends Component { static propTypes = { data: PropTypes.array.isRequired, name: PropTypes.string.isRequired, domain: PropTypes.array.isRequired, width: PropTypes.number.isRequired, height: PropTypes.number.isRequired, padding: PropTypes.exact({ top: PropTypes.number, bottom: PropTypes.number, left: PropTypes.number, right: PropTypes.number, }).isRequired, }; _getXScale = () => { const { width, padding: {left, right}, } = this.props; return scaleLinear() .domain([0, 1]) .range([left, width - right]); }; _getYScale = () => { const { height, domain, padding: {top, bottom}, } = this.props; return scaleLinear() .domain(domain) .range([height - bottom, top]); }; _renderLegend() { const { data, name, width, padding: {right, top}, } = this.props; if (!data) { return null; } return ( <React.Fragment> <text x={width - right - 23} y={top + 5} dominantBaseline="middle" textAnchor="end" fontSize={10} > {name} </text> <line x1={width - right - 20} y1={top + 5} x2={width - right - 5} y2={top + 5} stroke="#3399ff" strokeWidth={2} /> </React.Fragment> ); } _renderXAxis() { const { width, height, padding: {bottom, left, right}, } = this.props; const xScale = this._getXScale(); const format = d3Format('.2d'); return ( <React.Fragment> <line x1={left} y1={height - bottom} x2={width - right} y2={height - bottom} stroke="black" strokeWidth={1} /> {xScale.ticks(10).map(t => { const x = xScale(t); return ( <React.Fragment key={t}> <line x1={x} y1={height - bottom} x2={x} y2={height - bottom + 4} stroke="black" strokeWidth={1} /> <text x={x} y={height - bottom + 6} dominantBaseline="hanging" textAnchor="middle" fontSize={10} > {`${format(t * 100)}%`} </text> </React.Fragment> ); })} </React.Fragment> ); } _renderYAxis() { const { height, padding: {bottom, left, top}, } = this.props; const yScale = this._getYScale(); const format = d3Format('.1f'); return ( <React.Fragment> <line x1={left} y1={height - bottom} x2={left} y2={top} stroke="black" strokeWidth={1} /> {yScale.ticks(10).map(t => { const y = yScale(t); return ( <React.Fragment key={t}> <line x1={left} y1={y} x2={left - 4} y2={y} stroke="black" strokeWidth={1} /> <text x={left - 6} y={y} dominantBaseline="middle" textAnchor="end" fontSize={10} > {format(t)} </text> </React.Fragment> ); })} </React.Fragment> ); } _renderGrids() { const { width, height, padding: {left, right, top}, } = this.props; const xScale = this._getXScale(); const yScale = this._getYScale(); return ( <React.Fragment> {yScale.ticks(10).map(t => { const y = yScale(t); return ( <line key={`y-${t}`} x1={left} y1={y} x2={width - right} y2={y} stroke="lightgray" strokeWidth={1} /> ); })} {xScale.ticks(10).map(t => { const x = xScale(t); return ( <line key={`x-${t}`} x1={x} y1={top} x2={x} y2={height - top} stroke="lightgray" strokeWidth={1} /> ); })} </React.Fragment> ); } _renderLine() { const {data} = this.props; if (!data) { return null; } const xScale = this._getXScale(); const yScale = this._getYScale(); const getSVGLine = d3Line() .x(d => xScale(d.x)) .y(d => yScale(d.y)); return ( <path d={getSVGLine(data)} fill="none" stroke="#3399ff" strokeWidth={2} /> ); } _renderDots() { const {data} = this.props; if (!data) { return null; } const xScale = this._getXScale(); const yScale = this._getYScale(); return ( <g> {data.map((d, i) => ( <circle key={i} cx={xScale(d.x)} cy={yScale(d.y)} r={3} fill="#3399ff" /> ))} </g> ); } render() { const {width, height} = this.props; return ( <svg width={width} height={height}> {this._renderGrids()} {this._renderLine()} {this._renderDots()} {this._renderXAxis()} {this._renderYAxis()} {this._renderLegend()} </svg> ); } }