Skip to content

Commit

Permalink
feat: add sankey chart with loops (#77)
Browse files Browse the repository at this point in the history
* feat(chart): add basic working look sankey chart

* fix(doc): update readme and remove dead code

* fix(clean): clean up both style and code

* fix(reorg): reogranize for clarity

* fix(path): remove dev path to chart

* fix(lint): remove lint

* fix(pr): fixes for PR

* fix(path): fix dev path

* fix(lint): remove unused
  • Loading branch information
trebor authored and zhaoyongjie committed Nov 26, 2021
1 parent e230081 commit c0920f6
Show file tree
Hide file tree
Showing 12 changed files with 412 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## @superset-ui/legacy-plugin-chart-sankey-loop

[![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-sankey.svg?style=flat-square)](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-sankey.svg?style=flat-square)
[![David (path)](https://img.shields.io/david/apache-superset/superset-ui-plugins.svg?path=packages%2Fsuperset-ui-legacy-plugin-chart-sankey&style=flat-square)](https://david-dm.org/apache-superset/superset-ui-plugins?path=packages/superset-ui-legacy-plugin-chart-sankey)

This plugin provides Sankey Diagram with loops for Superset.

### Usage

Configure `key`, which can be any `string`, and register the plugin. This `key` will be used to lookup this chart throughout the app.

```js
import SankeyLoopChartPlugin from '@superset-ui/legacy-plugin-chart-sankey-loop';

new SankeyLoopChartPlugin()
.configure({ key: 'sankey' })
.register();
```

Then use it via `SuperChart`. See [storybook](https://apache-superset.github.io/superset-ui-plugins/?selectedKind=plugin-chart-sankey-loop) for more details.

```js
<SuperChart
chartType="sankey-loop"
chartProps={{
width: 600,
height: 600,
formData: {...},
payload: {
data: {...},
},
}}
/>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@superset-ui/legacy-plugin-chart-sankey-loop",
"version": "0.10.11",
"description": "Superset Legacy Chart - Sankey Diagram with Loops",
"sideEffects": [
"*.css"
],
"main": "lib/index.js",
"module": "esm/index.js",
"files": [
"esm",
"lib"
],
"repository": {
"type": "git",
"url": "git+https://github.com/apache-superset/superset-ui-plugins.git"
},
"keywords": [
"superset"
],
"author": "Superset",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/apache-superset/superset-ui-plugins/issues"
},
"homepage": "https://github.com/apache-superset/superset-ui-plugins#readme",
"publishConfig": {
"access": "public"
},
"dependencies": {
"d3-sankey-diagram": "^0.7.3",
"d3-selection": "^1.4.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"@superset-ui/chart": "^0.10.0 || ^0.11.0",
"@superset-ui/color": "^0.10.0 || ^0.11.0",
"@superset-ui/number-format": "^0.10.0 || ^0.11.0",
"@superset-ui/translation": "^0.10.0 || ^0.11.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* 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.
*/
import { reactify } from '@superset-ui/chart';
import Component from './SankeyLoop';

export default reactify(Component);
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* 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.
*/

.superset-legacy-chart-sankey-loop .node rect {
cursor: move;
fill-opacity: .9;
shape-rendering: crispEdges;
}

.superset-legacy-chart-sankey-loop .node text {
pointer-events: none;
text-shadow: 0 1px 0 #fff;
}

.superset-legacy-chart-sankey-loop .link {
fill: none;
stroke: #000;
stroke-opacity: .2;
}

.superset-legacy-chart-sankey-loop .link:hover {
stroke-opacity: .5;
}

.superset-legacy-chart-sankey-loop .link path {
opacity: .2;
stroke-opacity: 0;
}

.superset-legacy-chart-sankey-loop .link:hover path {
opacity: .5
}

.superset-legacy-chart-sankey-loop .link text {
fill: #666;
font-size: 10px
}

.superset-legacy-chart-sankey-loop .link:hover text {
opacity: 1
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/**
* 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, no-magic-numbers, sort-keys, babel/no-invalid-this */
import PropTypes from 'prop-types';
import { select } from 'd3-selection';
import { sankeyDiagram, sankey } from 'd3-sankey-diagram';
import { CategoricalColorNamespace } from '@superset-ui/color';
import { getNumberFormatter, NumberFormats } from '@superset-ui/number-format';

import './SankeyLoop.css';

// a problem with 'd3-sankey-diagram' is that the sankey().extent() paramters, which
// informs the layout of the bounding box of the sankey columns, does not account
// for labels and paths which happen to be layedout outside that rectangle.
// for that reason i've selected relatively large default left/right margins, and have
// made 'margin' a property. i have raised an issue in the chart repo:
//
// https://github.com/ricklupton/d3-sankey-diagram/issues/20

const defaultMargin = {
top: 0,
right: 80,
bottom: 0,
left: 80,
};

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,
margin: PropTypes.shape({
top: PropTypes.number,
right: PropTypes.number,
bottom: PropTypes.number,
left: PropTypes.number,
}),
};

const percentFormat = getNumberFormatter(NumberFormats.PERCENT_1_POINT);
const countFormat = getNumberFormatter();

function computeGraph(links) {
// this assumes source and target are string values
const nodes = Array.from(
links.reduce((set, { source, target }) => set.add(source).add(target), new Set()),
).map(id => ({ id, name: id }));

return {
nodes,

// links are shallow copied as the chart layout modifies them, and it is best to
// leave the passed data un-altered
links: links.map(d => ({ ...d })),
};
}

function SankeyLoop(element, props) {
const { data, width, height, colorScheme } = props;
const color = CategoricalColorNamespace.getScale(colorScheme);
const margin = { ...defaultMargin, ...props.margin };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;

const layout = sankey()
.nodeId(d => d.id)
.extent([[margin.left, margin.top], [innerWidth, innerHeight]]);

const diagram = sankeyDiagram()
.nodeTitle(d => d.name)
.linkTitle(
({ source: { name: sName, value: sValue }, target: { name: tName }, value }) =>
`${sName}${tName}: ${countFormat(value)} (${percentFormat(value / sValue)})`,
)
.linkColor(d => color(d.source.name));

const svg = select(element)
.append('svg')
.classed('superset-legacy-chart-sankey-loop', true)
.style('width', width)
.style('height', height)
.datum(layout(computeGraph(data)))
.call(diagram);

svg
.selectAll('g.link')
.classed('link', true)
.append('text')
.attr('x', d => d.points[0].x)
.attr('y', d => d.points[0].y)
.attr('dy', 3)
.attr('dx', 2)
.text(d => `${countFormat(d.value)} (${percentFormat(d.value / d.source.value)})`);
}

SankeyLoop.displayName = 'SankeyLoop';
SankeyLoop.propTypes = propTypes;

export default SankeyLoop;
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* 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.
*/
import { t } from '@superset-ui/translation';
import { ChartMetadata, ChartPlugin } from '@superset-ui/chart';
import transformProps from './transformProps';
import thumbnail from './images/thumbnail.png';

const metadata = new ChartMetadata({
credits: ['https://github.com/ricklupton/d3-sankey-diagram'],
description: '',
name: t('Sankey Diagram with Loops'),
thumbnail,
useLegacyApi: true,
});

export default class SankeyChartPlugin extends ChartPlugin {
constructor() {
super({
loadChart: () => import('./ReactSankeyLoop.js'),
metadata,
transformProps,
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* 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 sort-keys */
export default function transformProps(chartProps) {
const { width, height, formData, payload, margin } = chartProps;
const { colorScheme } = formData;

return {
width,
height,
data: payload.data,
colorScheme,
margin,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* eslint-disable no-magic-numbers */
import React from 'react';
import { SuperChart } from '@superset-ui/chart';
import data from './data';

export default [
{
renderStory: () => (
<SuperChart
chartType="sankey-loop"
chartProps={{
formData: {
colorScheme: 'd3Category10',
},
height: 400,
payload: { data },
width: 400,
}}
/>
),
storyName: 'Basic',
storyPath: 'legacy-|plugin-chart-sankey-loop|SankeyLoopChartPlugin',
},
];

0 comments on commit c0920f6

Please sign in to comment.