UNPKG

@mcdevsl/superset-ui

Version:
198 lines (174 loc) 5.7 kB
/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* eslint-disable no-continue, no-bitwise */ /* eslint-disable react/jsx-sort-default-props */ /* eslint-disable react/sort-prop-types */ import React from 'react'; import PropTypes from 'prop-types'; import { extent as d3Extent } from 'd3-array'; import { scaleLinear } from 'd3-scale'; export const DEFAULT_COLORS = [ '#313695', '#4575b4', '#74add1', '#abd9e9', '#fee090', '#fdae61', '#f46d43', '#d73027', ]; const propTypes = { className: PropTypes.string, width: PropTypes.number, height: PropTypes.number, data: PropTypes.arrayOf( PropTypes.shape({ y: PropTypes.number, }), ).isRequired, bands: PropTypes.number, colors: PropTypes.arrayOf(PropTypes.string), colorScale: PropTypes.string, mode: PropTypes.string, offsetX: PropTypes.number, title: PropTypes.string, yDomain: PropTypes.arrayOf(PropTypes.number), }; const defaultProps = { className: '', width: 800, height: 20, bands: DEFAULT_COLORS.length >> 1, colors: DEFAULT_COLORS, colorScale: 'series', mode: 'offset', offsetX: 0, title: '', yDomain: undefined, }; class HorizonRow extends React.PureComponent { componentDidMount() { this.drawChart(); } componentDidUpdate() { this.drawChart(); } componentWillUnmount() { this.canvas = null; } drawChart() { if (this.canvas) { const { data: rawData, yDomain, width, height, bands, colors, colorScale, offsetX, mode, } = this.props; const data = colorScale === 'change' ? rawData.map(d => ({ ...d, y: d.y - rawData[0].y })) : rawData; const context = this.canvas.getContext('2d'); context.imageSmoothingEnabled = false; context.clearRect(0, 0, width, height); // Reset transform context.setTransform(1, 0, 0, 1, 0, 0); context.translate(0.5, 0.5); const step = width / data.length; // the data frame currently being shown: const startIndex = Math.floor(Math.max(0, -(offsetX / step))); const endIndex = Math.floor(Math.min(data.length, startIndex + width / step)); // skip drawing if there's no data to be drawn if (startIndex > data.length) { return; } // Create y-scale const [min, max] = yDomain || d3Extent(data, d => d.y); const y = scaleLinear() .domain([0, Math.max(-min, max)]) .range([0, height]); // we are drawing positive & negative bands separately to avoid mutating canvas state // http://www.html5rocks.com/en/tutorials/canvas/performance/ let hasNegative = false; // draw positive bands let value; let bExtents; for (let b = 0; b < bands; b += 1) { context.fillStyle = colors[bands + b]; // Adjust the range based on the current band index. bExtents = (b + 1 - bands) * height; y.range([bands * height + bExtents, bExtents]); // only the current data frame is being drawn i.e. what's visible: for (let i = startIndex; i < endIndex; i += 1) { value = data[i].y; if (value <= 0) { hasNegative = true; continue; } if (value !== undefined) { context.fillRect(offsetX + i * step, y(value), step + 1, y(0) - y(value)); } } } // draw negative bands if (hasNegative) { // mirror the negative bands, by flipping the canvas if (mode === 'offset') { context.translate(0, height); context.scale(1, -1); } for (let b = 0; b < bands; b += 1) { context.fillStyle = colors[bands - b - 1]; // Adjust the range based on the current band index. bExtents = (b + 1 - bands) * height; y.range([bands * height + bExtents, bExtents]); // only the current data frame is being drawn i.e. what's visible: for (let ii = startIndex; ii < endIndex; ii += 1) { value = data[ii].y; if (value >= 0) { continue; } context.fillRect(offsetX + ii * step, y(-value), step + 1, y(0) - y(-value)); } } } } } render() { const { className, title, width, height } = this.props; return ( <div className={`horizon-row ${className}`}> <span className="title">{title}</span> <canvas ref={c => { this.canvas = c; }} width={width} height={height} /> </div> ); } } HorizonRow.propTypes = propTypes; HorizonRow.defaultProps = defaultProps; export default HorizonRow;