123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- function _extends() { _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; }; return _extends.apply(this, arguments); }
- /**
- * 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-param-reassign */
- /* eslint-disable react/sort-prop-types */
- import d3 from 'd3';
- import PropTypes from 'prop-types';
- import { sankey as d3Sankey } from 'd3-sankey';
- import { CategoricalColorNamespace } from '@superset-ui/color';
- import { getNumberFormatter, NumberFormats } from '@superset-ui/number-format';
- import './Sankey.css';
- const propTypes = {
- data: PropTypes.arrayOf(PropTypes.shape({
- source: PropTypes.string,
- target: PropTypes.string,
- value: PropTypes.number
- })),
- width: PropTypes.number,
- height: PropTypes.number,
- colorScheme: PropTypes.string
- };
- const formatNumber = getNumberFormatter(NumberFormats.FLOAT);
- function Sankey(element, props) {
- const {
- data,
- width,
- height,
- colorScheme
- } = props;
- const div = d3.select(element);
- div.classed('superset-legacy-chart-sankey', true);
- const margin = {
- top: 5,
- right: 5,
- bottom: 5,
- left: 5
- };
- const innerWidth = width - margin.left - margin.right;
- const innerHeight = height - margin.top - margin.bottom;
- div.selectAll('*').remove();
- const svg = div.append('svg').attr('width', innerWidth + margin.left + margin.right).attr('height', innerHeight + margin.top + margin.bottom).append('g').attr('transform', "translate(" + margin.left + "," + margin.top + ")");
- const tooltip = div.append('div').attr('class', 'sankey-tooltip').style('opacity', 0);
- const colorFn = CategoricalColorNamespace.getScale(colorScheme);
- const sankey = d3Sankey().nodeWidth(15).nodePadding(10).size([innerWidth, innerHeight]);
- const path = sankey.link();
- let nodes = {}; // Compute the distinct nodes from the links.
- const links = data.map(row => {
- const link = _extends({}, row);
- link.source = nodes[link.source] || (nodes[link.source] = {
- name: link.source
- });
- link.target = nodes[link.target] || (nodes[link.target] = {
- name: link.target
- });
- link.value = Number(link.value);
- return link;
- });
- nodes = d3.values(nodes);
- sankey.nodes(nodes).links(links).layout(32);
- function getTooltipHtml(d) {
- let html;
- if (d.sourceLinks) {
- // is node
- html = d.name + " Value: <span class='emph'>" + formatNumber(d.value) + "</span>";
- } else {
- const val = formatNumber(d.value);
- const sourcePercent = d3.round(d.value / d.source.value * 100, 1);
- const targetPercent = d3.round(d.value / d.target.value * 100, 1);
- html = ["<div class=''>Path Value: <span class='emph'>", val, '</span></div>', "<div class='percents'>", "<span class='emph'>", Number.isFinite(sourcePercent) ? sourcePercent : '100', '%</span> of ', d.source.name, '<br/>', "<span class='emph'>" + (Number.isFinite(targetPercent) ? targetPercent : '--') + "%</span> of ", d.target.name, '</div>'].join('');
- }
- return html;
- }
- function onmouseover(d) {
- tooltip.html(() => getTooltipHtml(d)).transition().duration(200).style('left', d3.event.offsetX + 10 + "px").style('top', d3.event.offsetY + 10 + "px").style('opacity', 0.95);
- }
- function onmouseout() {
- tooltip.transition().duration(100).style('opacity', 0);
- }
- const link = svg.append('g').selectAll('.link').data(links).enter().append('path').attr('class', 'link').attr('d', path).style('stroke-width', d => Math.max(1, d.dy)).sort((a, b) => b.dy - a.dy).on('mouseover', onmouseover).on('mouseout', onmouseout);
- function dragmove(d) {
- d3.select(this).attr('transform', "translate(" + d.x + "," + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")");
- sankey.relayout();
- link.attr('d', path);
- }
- const node = svg.append('g').selectAll('.node').data(nodes).enter().append('g').attr('class', 'node').attr('transform', d => "translate(" + d.x + "," + d.y + ")").call(d3.behavior.drag().origin(d => d).on('dragstart', function dragStart() {
- this.parentNode.append(this);
- }).on('drag', dragmove));
- const minRectHeight = 5;
- node.append('rect').attr('height', d => d.dy > minRectHeight ? d.dy : minRectHeight).attr('width', sankey.nodeWidth()).style('fill', d => {
- const name = d.name || 'N/A';
- d.color = colorFn(name.replace(/ .*/, ''));
- return d.color;
- }).style('stroke', d => d3.rgb(d.color).darker(2)).on('mouseover', onmouseover).on('mouseout', onmouseout);
- node.append('text').attr('x', -6).attr('y', d => d.dy / 2).attr('dy', '.35em').attr('text-anchor', 'end').attr('transform', null).text(d => d.name).filter(d => d.x < innerWidth / 2).attr('x', 6 + sankey.nodeWidth()).attr('text-anchor', 'start');
- }
- Sankey.displayName = 'Sankey';
- Sankey.propTypes = propTypes;
- export default Sankey;
|