UNPKG

react-vis

Version:

Data visualization library based on React and d3.

360 lines (303 loc) 13.4 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; // Copyright (c) 2016 - 2017 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _d3Scale = require('d3-scale'); var _d3Format = require('d3-format'); var _animation = require('../animation'); var _xyPlot = require('../plot/xy-plot'); var _xyPlot2 = _interopRequireDefault(_xyPlot); var _theme = require('../theme'); var _chartUtils = require('../utils/chart-utils'); var _lineSeries = require('../plot/series/line-series'); var _lineSeries2 = _interopRequireDefault(_lineSeries); var _lineMarkSeries = require('../plot/series/line-mark-series'); var _lineMarkSeries2 = _interopRequireDefault(_lineMarkSeries); var _labelSeries = require('../plot/series/label-series'); var _labelSeries2 = _interopRequireDefault(_labelSeries); var _decorativeAxis = require('../plot/axis/decorative-axis'); var _decorativeAxis2 = _interopRequireDefault(_decorativeAxis); var _highlight = require('../plot/highlight'); var _highlight2 = _interopRequireDefault(_highlight); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var predefinedClassName = 'rv-parallel-coordinates-chart'; var DEFAULT_FORMAT = (0, _d3Format.format)('.2r'); /** * Generate axes for each of the domains * @param {Object} props - props.animation {Boolean} - props.domains {Array} array of object specifying the way each axis is to be plotted - props.style {object} style object for the whole chart - props.tickFormat {Function} formatting function for axes * @return {Array} the plotted axis components */ function getAxes(props) { var animation = props.animation, domains = props.domains, style = props.style, tickFormat = props.tickFormat; return domains.map(function (domain, index) { var sortedDomain = domain.domain; var domainTickFormat = function domainTickFormat(t) { return domain.tickFormat ? domain.tickFormat(t) : tickFormat(t); }; return _react2.default.createElement(_decorativeAxis2.default, { animation: animation, key: index + '-axis', axisStart: { x: domain.name, y: 0 }, axisEnd: { x: domain.name, y: 1 }, axisDomain: sortedDomain, numberOfTicks: 5, tickValue: domainTickFormat, style: style.axes }); }); } /** * Generate labels for the ends of the axes * @param {Object} props - props.domains {Array} array of object specifying the way each axis is to be plotted - props.style {object} style object for just the labels * @return {Array} the prepped data for the labelSeries */ function getLabels(props) { var domains = props.domains, style = props.style; return domains.map(function (domain, index) { return { x: domain.name, y: 1.1, label: domain.name, style: style }; }); } /** * Generate the actual lines to be plotted * @param {Object} props - props.animation {Boolean} - props.data {Array} array of object specifying what values are to be plotted - props.domains {Array} array of object specifying the way each axis is to be plotted - props.style {object} style object for the whole chart - props.showMarks {Bool} whether or not to use the line mark series * @return {Array} the plotted axis components */ function getLines(props) { var animation = props.animation, brushFilters = props.brushFilters, colorRange = props.colorRange, domains = props.domains, data = props.data, style = props.style, showMarks = props.showMarks; var scales = domains.reduce(function (acc, _ref) { var domain = _ref.domain, name = _ref.name; acc[name] = (0, _d3Scale.scaleLinear)().domain(domain).range([0, 1]); return acc; }, {}); // const return data.map(function (row, rowIndex) { var withinFilteredRange = true; var mappedData = domains.map(function (domain, index) { var getValue = domain.getValue, name = domain.name; // watch out! Gotcha afoot // yVal after being scale is in [0, 1] range var yVal = scales[name](getValue ? getValue(row) : row[name]); var filter = brushFilters[name]; // filter value after being scale back from pixel space is also in [0, 1] if (filter && (yVal < filter.min || yVal > filter.max)) { withinFilteredRange = false; } return { x: name, y: yVal }; }); var selectedName = predefinedClassName + '-line'; var unselectedName = selectedName + ' ' + predefinedClassName + '-line-unselected'; var lineProps = { animation: animation, className: withinFilteredRange ? selectedName : unselectedName, key: rowIndex + '-polygon', data: mappedData, color: row.color || colorRange[rowIndex % colorRange.length], style: _extends({}, style.lines, row.style || {}) }; if (!withinFilteredRange) { lineProps.style = _extends({}, lineProps.style, style.deselectedLineStyle); } return showMarks ? _react2.default.createElement(_lineMarkSeries2.default, lineProps) : _react2.default.createElement(_lineSeries2.default, lineProps); }); } var ParallelCoordinates = function (_Component) { _inherits(ParallelCoordinates, _Component); function ParallelCoordinates() { var _ref2; var _temp, _this, _ret; _classCallCheck(this, ParallelCoordinates); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref2 = ParallelCoordinates.__proto__ || Object.getPrototypeOf(ParallelCoordinates)).call.apply(_ref2, [this].concat(args))), _this), _this.state = { brushFilters: {} }, _temp), _possibleConstructorReturn(_this, _ret); } _createClass(ParallelCoordinates, [{ key: 'render', value: function render() { var _this2 = this; var brushFilters = this.state.brushFilters; var _props = this.props, animation = _props.animation, brushing = _props.brushing, className = _props.className, children = _props.children, colorRange = _props.colorRange, data = _props.data, domains = _props.domains, height = _props.height, hideInnerMostValues = _props.hideInnerMostValues, margin = _props.margin, onMouseLeave = _props.onMouseLeave, onMouseEnter = _props.onMouseEnter, showMarks = _props.showMarks, style = _props.style, tickFormat = _props.tickFormat, width = _props.width; var axes = getAxes({ domains: domains, animation: animation, hideInnerMostValues: hideInnerMostValues, style: style, tickFormat: tickFormat }); var lines = getLines({ animation: animation, brushFilters: brushFilters, colorRange: colorRange, domains: domains, data: data, showMarks: showMarks, style: style }); var labelSeries = _react2.default.createElement(_labelSeries2.default, { animation: true, key: className, className: predefinedClassName + '-label', data: getLabels({ domains: domains, style: style.labels }) }); var _getInnerDimensions = (0, _chartUtils.getInnerDimensions)(this.props, _chartUtils.DEFAULT_MARGINS), marginLeft = _getInnerDimensions.marginLeft, marginRight = _getInnerDimensions.marginRight; return _react2.default.createElement( _xyPlot2.default, { height: height, width: width, margin: margin, dontCheckIfEmpty: true, className: className + ' ' + predefinedClassName, onMouseLeave: onMouseLeave, onMouseEnter: onMouseEnter, xType: 'ordinal', yDomain: [0, 1] }, children, axes.concat(lines).concat(labelSeries), brushing && domains.map(function (d) { var trigger = function trigger(row) { _this2.setState({ brushFilters: _extends({}, brushFilters, _defineProperty({}, d.name, row ? { min: row.bottom, max: row.top } : null)) }); }; return _react2.default.createElement(_highlight2.default, { key: d.name, drag: true, highlightX: d.name, onBrushEnd: trigger, onDragEnd: trigger, highlightWidth: (width - marginLeft - marginRight) / domains.length, enableX: false }); }) ); } }]); return ParallelCoordinates; }(_react.Component); ParallelCoordinates.displayName = 'ParallelCoordinates'; ParallelCoordinates.propTypes = { animation: _animation.AnimationPropType, brushing: _propTypes2.default.bool, className: _propTypes2.default.string, colorType: _propTypes2.default.string, colorRange: _propTypes2.default.arrayOf(_propTypes2.default.string), data: _propTypes2.default.arrayOf(_propTypes2.default.object).isRequired, domains: _propTypes2.default.arrayOf(_propTypes2.default.shape({ name: _propTypes2.default.string.isRequired, domain: _propTypes2.default.arrayOf(_propTypes2.default.number).isRequired, tickFormat: _propTypes2.default.func })).isRequired, height: _propTypes2.default.number.isRequired, margin: _chartUtils.MarginPropType, style: _propTypes2.default.shape({ axes: _propTypes2.default.object, labels: _propTypes2.default.object, lines: _propTypes2.default.object }), showMarks: _propTypes2.default.bool, tickFormat: _propTypes2.default.func, width: _propTypes2.default.number.isRequired }; ParallelCoordinates.defaultProps = { className: '', colorType: 'category', colorRange: _theme.DISCRETE_COLOR_RANGE, style: { axes: { line: {}, ticks: {}, text: {} }, labels: { fontSize: 10, textAnchor: 'middle' }, lines: { strokeWidth: 1, strokeOpacity: 1 }, deselectedLineStyle: { strokeOpacity: 0.1 } }, tickFormat: DEFAULT_FORMAT }; exports.default = ParallelCoordinates;