mirror of https://github.com/apache/superset.git
refactor: Migrates legacy Sunburst charts to ECharts and removes legacy code (#26350)
This commit is contained in:
parent
cf20b3439c
commit
4d9144eca5
|
@ -25,7 +25,7 @@ export const WORLD_HEALTH_CHARTS = [
|
|||
{ name: 'Most Populated Countries', viz: 'table' },
|
||||
{ name: "World's Population", viz: 'big_number' },
|
||||
{ name: 'Growth Rate', viz: 'line' },
|
||||
{ name: 'Rural Breakdown', viz: 'sunburst' },
|
||||
{ name: 'Rural Breakdown', viz: 'sunburst_v2' },
|
||||
{ name: "World's Pop Growth", viz: 'area' },
|
||||
{ name: 'Life Expectancy VS Rural %', viz: 'bubble' },
|
||||
{ name: 'Treemap', viz: 'treemap_v2' },
|
||||
|
|
|
@ -18,17 +18,17 @@
|
|||
*/
|
||||
describe('Visualization > Sunburst', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept('POST', '/superset/explore_json/**').as('getJson');
|
||||
cy.intercept('POST', '/api/v1/chart/data**').as('chartData');
|
||||
});
|
||||
|
||||
const SUNBURST_FORM_DATA = {
|
||||
datasource: '2__table',
|
||||
viz_type: 'sunburst',
|
||||
viz_type: 'sunburst_v2',
|
||||
slice_id: 47,
|
||||
granularity_sqla: 'year',
|
||||
time_grain_sqla: 'P1D',
|
||||
time_range: 'No filter',
|
||||
groupby: ['region'],
|
||||
columns: ['region'],
|
||||
metric: 'sum__SP_POP_TOTL',
|
||||
adhoc_filters: [],
|
||||
row_limit: 50000,
|
||||
|
@ -37,32 +37,35 @@ describe('Visualization > Sunburst', () => {
|
|||
|
||||
function verify(formData) {
|
||||
cy.visitChartByParams(formData);
|
||||
cy.verifySliceSuccess({ waitAlias: '@getJson', chartSelector: 'svg' });
|
||||
cy.verifySliceSuccess({ waitAlias: '@chartData' });
|
||||
}
|
||||
|
||||
it('should work without secondary metric', () => {
|
||||
// requires the ability to render charts using SVG only for tests
|
||||
it.skip('should work without secondary metric', () => {
|
||||
verify(SUNBURST_FORM_DATA);
|
||||
// There should be 7 visible arcs + 1 hidden
|
||||
cy.get('.chart-container svg g#arcs path').should('have.length', 8);
|
||||
cy.get('.chart-container svg g path').should('have.length', 7);
|
||||
});
|
||||
|
||||
it('should work with secondary metric', () => {
|
||||
// requires the ability to render charts using SVG only for tests
|
||||
it.skip('should work with secondary metric', () => {
|
||||
verify({
|
||||
...SUNBURST_FORM_DATA,
|
||||
secondary_metric: 'sum__SP_RUR_TOTL',
|
||||
});
|
||||
cy.get('.chart-container svg g#arcs path').should('have.length', 8);
|
||||
cy.get('.chart-container svg g path').should('have.length', 7);
|
||||
});
|
||||
|
||||
it('should work with multiple groupbys', () => {
|
||||
// requires the ability to render charts using SVG only for tests
|
||||
it.skip('should work with multiple columns', () => {
|
||||
verify({
|
||||
...SUNBURST_FORM_DATA,
|
||||
groupby: ['region', 'country_name'],
|
||||
columns: ['region', 'country_name'],
|
||||
});
|
||||
cy.get('.chart-container svg g#arcs path').should('have.length', 117);
|
||||
cy.get('.chart-container svg g path').should('have.length', 221);
|
||||
});
|
||||
|
||||
it('should work with filter', () => {
|
||||
// requires the ability to render charts using SVG only for tests
|
||||
it.skip('should work with filter', () => {
|
||||
verify({
|
||||
...SUNBURST_FORM_DATA,
|
||||
adhoc_filters: [
|
||||
|
@ -77,7 +80,7 @@ describe('Visualization > Sunburst', () => {
|
|||
},
|
||||
],
|
||||
});
|
||||
cy.get('.chart-container svg g#arcs path').should('have.length', 3);
|
||||
cy.get('.chart-container svg g path').should('have.length', 2);
|
||||
});
|
||||
|
||||
it('should allow type to search color schemes', () => {
|
||||
|
|
|
@ -39,7 +39,6 @@
|
|||
"@superset-ui/legacy-plugin-chart-rose": "file:./plugins/legacy-plugin-chart-rose",
|
||||
"@superset-ui/legacy-plugin-chart-sankey": "file:./plugins/legacy-plugin-chart-sankey",
|
||||
"@superset-ui/legacy-plugin-chart-sankey-loop": "file:./plugins/legacy-plugin-chart-sankey-loop",
|
||||
"@superset-ui/legacy-plugin-chart-sunburst": "file:./plugins/legacy-plugin-chart-sunburst",
|
||||
"@superset-ui/legacy-plugin-chart-world-map": "file:./plugins/legacy-plugin-chart-world-map",
|
||||
"@superset-ui/legacy-preset-chart-deckgl": "file:./plugins/legacy-preset-chart-deckgl",
|
||||
"@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3",
|
||||
|
@ -18338,10 +18337,6 @@
|
|||
"resolved": "plugins/legacy-plugin-chart-sankey-loop",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/legacy-plugin-chart-sunburst": {
|
||||
"resolved": "plugins/legacy-plugin-chart-sunburst",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@superset-ui/legacy-plugin-chart-world-map": {
|
||||
"resolved": "plugins/legacy-plugin-chart-world-map",
|
||||
"link": true
|
||||
|
@ -62257,7 +62252,6 @@
|
|||
"@superset-ui/legacy-plugin-chart-rose": "*",
|
||||
"@superset-ui/legacy-plugin-chart-sankey": "*",
|
||||
"@superset-ui/legacy-plugin-chart-sankey-loop": "*",
|
||||
"@superset-ui/legacy-plugin-chart-sunburst": "*",
|
||||
"@superset-ui/legacy-plugin-chart-time-table": "*",
|
||||
"@superset-ui/legacy-plugin-chart-world-map": "*",
|
||||
"@superset-ui/legacy-preset-chart-deckgl": "*",
|
||||
|
@ -63291,20 +63285,6 @@
|
|||
"react": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-sunburst": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-sunburst",
|
||||
"version": "0.18.25",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"d3": "^3.5.17",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"plugins/legacy-plugin-chart-time-table": {
|
||||
"name": "@superset-ui/legacy-plugin-chart-time-table",
|
||||
"version": "0.0.1",
|
||||
|
@ -78157,13 +78137,6 @@
|
|||
"prop-types": "^15.8.1"
|
||||
}
|
||||
},
|
||||
"@superset-ui/legacy-plugin-chart-sunburst": {
|
||||
"version": "file:plugins/legacy-plugin-chart-sunburst",
|
||||
"requires": {
|
||||
"d3": "^3.5.17",
|
||||
"prop-types": "^15.8.1"
|
||||
}
|
||||
},
|
||||
"@superset-ui/legacy-plugin-chart-world-map": {
|
||||
"version": "file:plugins/legacy-plugin-chart-world-map",
|
||||
"requires": {
|
||||
|
|
|
@ -105,7 +105,6 @@
|
|||
"@superset-ui/legacy-plugin-chart-rose": "file:./plugins/legacy-plugin-chart-rose",
|
||||
"@superset-ui/legacy-plugin-chart-sankey": "file:./plugins/legacy-plugin-chart-sankey",
|
||||
"@superset-ui/legacy-plugin-chart-sankey-loop": "file:./plugins/legacy-plugin-chart-sankey-loop",
|
||||
"@superset-ui/legacy-plugin-chart-sunburst": "file:./plugins/legacy-plugin-chart-sunburst",
|
||||
"@superset-ui/legacy-plugin-chart-world-map": "file:./plugins/legacy-plugin-chart-world-map",
|
||||
"@superset-ui/legacy-preset-chart-deckgl": "file:./plugins/legacy-preset-chart-deckgl",
|
||||
"@superset-ui/legacy-preset-chart-nvd3": "file:./plugins/legacy-preset-chart-nvd3",
|
||||
|
|
|
@ -56,13 +56,13 @@ export const wordCloudFormData = {
|
|||
|
||||
export const sunburstFormData = {
|
||||
datasource: '2__table',
|
||||
viz_type: 'sunburst',
|
||||
viz_type: 'sunburst_v2',
|
||||
slice_id: 47,
|
||||
url_params: {},
|
||||
granularity_sqla: 'year',
|
||||
time_grain_sqla: 'P1D',
|
||||
time_range: '2011-01-01 : 2011-01-01',
|
||||
groupby: ['region', 'country_name'],
|
||||
columns: ['region', 'country_name'],
|
||||
metric: 'sum__SP_POP_TOTL',
|
||||
secondary_metric: 'sum__SP_RUR_TOTL',
|
||||
adhoc_filters: [],
|
||||
|
|
|
@ -80,7 +80,6 @@
|
|||
"@superset-ui/legacy-plugin-chart-rose": "*",
|
||||
"@superset-ui/legacy-plugin-chart-sankey": "*",
|
||||
"@superset-ui/legacy-plugin-chart-sankey-loop": "*",
|
||||
"@superset-ui/legacy-plugin-chart-sunburst": "*",
|
||||
"@superset-ui/legacy-plugin-chart-time-table": "*",
|
||||
"@superset-ui/legacy-plugin-chart-world-map": "*",
|
||||
"@superset-ui/legacy-preset-chart-deckgl": "*",
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* 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-magic-numbers */
|
||||
import React from 'react';
|
||||
import { SuperChart } from '@superset-ui/core';
|
||||
import SunburstChartPlugin from '@superset-ui/legacy-plugin-chart-sunburst';
|
||||
import ResizableChartDemo from '../../../shared/components/ResizableChartDemo';
|
||||
import data from './data';
|
||||
|
||||
new SunburstChartPlugin().configure({ key: 'sunburst' }).register();
|
||||
|
||||
export default {
|
||||
title: 'Legacy Chart Plugins/legacy-plugin-chart-sunburst',
|
||||
};
|
||||
|
||||
export const basic = () => (
|
||||
<SuperChart
|
||||
chartType="sunburst"
|
||||
width={400}
|
||||
height={400}
|
||||
queriesData={[{ data }]}
|
||||
formData={{
|
||||
colorScheme: 'd3Category10',
|
||||
metric: 'sum__SP_POP_TOTL',
|
||||
secondaryMetric: 'sum__SP_RUR_TOTL',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
export const resizable = () => (
|
||||
<ResizableChartDemo>
|
||||
{({ width, height }) => (
|
||||
<SuperChart
|
||||
chartType="sunburst"
|
||||
width={width}
|
||||
height={height}
|
||||
queriesData={[{ data }]}
|
||||
formData={{
|
||||
colorScheme: 'd3Category10',
|
||||
metric: 'sum__SP_POP_TOTL',
|
||||
secondaryMetric: 'sum__SP_RUR_TOTL',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ResizableChartDemo>
|
||||
);
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* 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, no-magic-numbers */
|
||||
export default [
|
||||
['East Asia & Pacific', 'China', 1344130000.0, 664363135.0],
|
||||
['South Asia', 'India', 1247446011.0, 857294797.0],
|
||||
['North America', 'United States', 311721632.0, 59414143.0],
|
||||
['East Asia & Pacific', 'Indonesia', 244808254.0, 120661092.0],
|
||||
['Latin America & Caribbean', 'Brazil', 200517584.0, 30833589.0],
|
||||
['South Asia', 'Pakistan', 173669648.0, 109399721.0],
|
||||
['Sub-Saharan Africa', 'Nigeria', 163770669.0, 91118725.0],
|
||||
['South Asia', 'Bangladesh', 153405612.0, 105504710.0],
|
||||
['Europe & Central Asia', 'Russian Federation', 142960868.0, 37552961.0],
|
||||
['East Asia & Pacific', 'Japan', 127817277.0, 11186568.0],
|
||||
];
|
|
@ -27,7 +27,6 @@ import {
|
|||
} from '@superset-ui/core';
|
||||
import { BigNumberChartPlugin } from '@superset-ui/plugin-chart-echarts';
|
||||
import LegacySankeyPlugin from '@superset-ui/legacy-plugin-chart-sankey';
|
||||
import LegacySunburstPlugin from '@superset-ui/legacy-plugin-chart-sunburst';
|
||||
import { WordCloudChartPlugin } from '@superset-ui/plugin-chart-word-cloud';
|
||||
|
||||
import {
|
||||
|
@ -50,8 +49,6 @@ new BigNumberChartPlugin().configure({ key: BIG_NUMBER }).register();
|
|||
// eslint-disable-next-line
|
||||
new LegacySankeyPlugin().configure({ key: SANKEY }).register();
|
||||
// eslint-disable-next-line
|
||||
new LegacySunburstPlugin().configure({ key: SUNBURST }).register();
|
||||
// eslint-disable-next-line
|
||||
new WordCloudChartPlugin().configure({ key: WORD_CLOUD }).register();
|
||||
|
||||
const VIS_TYPES = [BIG_NUMBER, SANKEY, SUNBURST, WORD_CLOUD, WORD_CLOUD_LEGACY];
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
<!--
|
||||
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.
|
||||
-->
|
||||
|
||||
# Change Log
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [0.18.0](https://github.com/apache-superset/superset-ui/compare/v0.17.87...v0.18.0) (2021-08-30)
|
||||
|
||||
**Note:** Version bump only for package @superset-ui/legacy-plugin-chart-sunburst
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.17.61](https://github.com/apache-superset/superset-ui/compare/v0.17.60...v0.17.61) (2021-07-02)
|
||||
|
||||
**Note:** Version bump only for package @superset-ui/legacy-plugin-chart-sunburst
|
|
@ -1,52 +0,0 @@
|
|||
<!--
|
||||
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-ui/legacy-plugin-chart-sunburst
|
||||
|
||||
[![Version](https://img.shields.io/npm/v/@superset-ui/legacy-plugin-chart-sunburst.svg?style=flat-square)](https://www.npmjs.com/package/@superset-ui/legacy-plugin-chart-sunburst)
|
||||
[![David (path)](https://img.shields.io/david/apache-superset/superset-ui-plugins.svg?path=packages%2Fsuperset-ui-legacy-plugin-chart-sunburst&style=flat-square)](https://david-dm.org/apache-superset/superset-ui-plugins?path=packages/superset-ui-legacy-plugin-chart-sunburst)
|
||||
|
||||
This plugin provides Sunburst 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 SunburstChartPlugin from '@superset-ui/legacy-plugin-chart-sunburst';
|
||||
|
||||
new SunburstChartPlugin().configure({ key: 'sunburst' }).register();
|
||||
```
|
||||
|
||||
Then use it via `SuperChart`. See
|
||||
[storybook](https://apache-superset.github.io/superset-ui-plugins/?selectedKind=plugin-chart-sunburst)
|
||||
for more details.
|
||||
|
||||
```js
|
||||
<SuperChart
|
||||
chartType="sunburst"
|
||||
width={600}
|
||||
height={600}
|
||||
formData={...}
|
||||
queriesData={[{
|
||||
data: {...},
|
||||
}]}
|
||||
/>
|
||||
```
|
|
@ -1,37 +0,0 @@
|
|||
{
|
||||
"name": "@superset-ui/legacy-plugin-chart-sunburst",
|
||||
"version": "0.18.25",
|
||||
"description": "Superset Legacy Chart - Sunburst",
|
||||
"keywords": [
|
||||
"superset"
|
||||
],
|
||||
"homepage": "https://github.com/apache/superset/tree/master/superset-frontend/plugins/legacy-plugin-chart-sunburst#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/apache/superset/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/apache/superset.git",
|
||||
"directory": "superset-frontend/plugins/legacy-plugin-chart-sunburst"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"author": "Superset",
|
||||
"main": "lib/index.js",
|
||||
"module": "esm/index.js",
|
||||
"files": [
|
||||
"esm",
|
||||
"lib"
|
||||
],
|
||||
"dependencies": {
|
||||
"d3": "^3.5.17",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@superset-ui/chart-controls": "*",
|
||||
"@superset-ui/core": "*",
|
||||
"react": "^16.13.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
/**
|
||||
* 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 React from 'react';
|
||||
import { reactify, styled } from '@superset-ui/core';
|
||||
import Component from './Sunburst';
|
||||
|
||||
const ReactComponent = reactify(Component);
|
||||
|
||||
const Sunburst = ({ className, ...otherProps }) => (
|
||||
<div className={className}>
|
||||
<ReactComponent {...otherProps} />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default styled(Sunburst)`
|
||||
${({ theme }) => `
|
||||
.superset-legacy-chart-sunburst text {
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
.superset-legacy-chart-sunburst path {
|
||||
stroke: ${theme.colors.grayscale.light2};
|
||||
stroke-width: 0.5px;
|
||||
}
|
||||
.superset-legacy-chart-sunburst .center-label {
|
||||
text-anchor: middle;
|
||||
fill: ${theme.colors.grayscale.dark1};
|
||||
pointer-events: none;
|
||||
}
|
||||
.superset-legacy-chart-sunburst .path-abs-percent {
|
||||
font-size: ${theme.typography.sizes.m}px;
|
||||
font-weight: ${theme.typography.weights.bold};
|
||||
}
|
||||
.superset-legacy-chart-sunburst .path-cond-percent {
|
||||
font-size: ${theme.typography.sizes.s}px;
|
||||
}
|
||||
.superset-legacy-chart-sunburst .path-metrics {
|
||||
color: ${theme.colors.grayscale.base};
|
||||
}
|
||||
.superset-legacy-chart-sunburst .path-ratio {
|
||||
color: ${theme.colors.grayscale.base};
|
||||
}
|
||||
|
||||
.superset-legacy-chart-sunburst .breadcrumbs text {
|
||||
font-weight: ${theme.typography.weights.bold};
|
||||
font-size: ${theme.typography.sizes.m}px;
|
||||
text-anchor: middle;
|
||||
fill: ${theme.colors.grayscale.dark1};
|
||||
}
|
||||
`}
|
||||
`;
|
|
@ -1,531 +0,0 @@
|
|||
/**
|
||||
* 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, react/sort-prop-types */
|
||||
import d3 from 'd3';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
getNumberFormatter,
|
||||
NumberFormats,
|
||||
CategoricalColorNamespace,
|
||||
getSequentialSchemeRegistry,
|
||||
t,
|
||||
} from '@superset-ui/core';
|
||||
import wrapSvgText from './utils/wrapSvgText';
|
||||
|
||||
const propTypes = {
|
||||
// Each row is an array of [hierarchy-lvl1, hierarchy-lvl2, metric1, metric2]
|
||||
// hierarchy-lvls are string. metrics are number
|
||||
data: PropTypes.arrayOf(PropTypes.array),
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
colorScheme: PropTypes.string,
|
||||
linearColorScheme: PropTypes.string,
|
||||
numberFormat: PropTypes.string,
|
||||
metrics: PropTypes.arrayOf(
|
||||
PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.object, // The metric object
|
||||
]),
|
||||
),
|
||||
};
|
||||
|
||||
function metricLabel(metric) {
|
||||
return typeof metric === 'string' || metric instanceof String
|
||||
? metric
|
||||
: metric.label;
|
||||
}
|
||||
|
||||
// Given a node in a partition layout, return an array of all of its ancestor
|
||||
// nodes, highest first, but excluding the root.
|
||||
function getAncestors(node) {
|
||||
const path = [];
|
||||
let current = node;
|
||||
while (current.parent) {
|
||||
path.unshift(current);
|
||||
current = current.parent;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
function buildHierarchy(rows) {
|
||||
const root = {
|
||||
name: 'root',
|
||||
children: [],
|
||||
};
|
||||
|
||||
// each record [groupby1val, groupby2val, (<string> or 0)n, m1, m2]
|
||||
rows.forEach(row => {
|
||||
const m1 = Number(row[row.length - 2]);
|
||||
const m2 = Number(row[row.length - 1]);
|
||||
const levels = row.slice(0, -2);
|
||||
if (Number.isNaN(m1)) {
|
||||
// e.g. if this is a header row
|
||||
return;
|
||||
}
|
||||
let currentNode = root;
|
||||
for (let level = 0; level < levels.length; level += 1) {
|
||||
const children = currentNode.children || [];
|
||||
const node = levels[level];
|
||||
const nodeName = node ? node.toString() : t('N/A');
|
||||
// If the next node has the name '0', it will
|
||||
const isLeafNode = level >= levels.length - 1 || levels[level + 1] === 0;
|
||||
let childNode;
|
||||
|
||||
if (!isLeafNode) {
|
||||
childNode = children.find(
|
||||
child => child.name === nodeName && child.level === level,
|
||||
);
|
||||
|
||||
if (!childNode) {
|
||||
childNode = {
|
||||
name: nodeName,
|
||||
children: [],
|
||||
level,
|
||||
};
|
||||
children.push(childNode);
|
||||
}
|
||||
currentNode = childNode;
|
||||
} else if (nodeName !== 0) {
|
||||
// Reached the end of the sequence; create a leaf node.
|
||||
childNode = {
|
||||
name: nodeName,
|
||||
m1,
|
||||
m2,
|
||||
};
|
||||
children.push(childNode);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function recurse(node) {
|
||||
if (node.children) {
|
||||
let sums;
|
||||
let m1 = 0;
|
||||
let m2 = 0;
|
||||
for (let i = 0; i < node.children.length; i += 1) {
|
||||
sums = recurse(node.children[i]);
|
||||
m1 += sums[0];
|
||||
m2 += sums[1];
|
||||
}
|
||||
node.m1 = m1;
|
||||
node.m2 = m2;
|
||||
}
|
||||
|
||||
return [node.m1, node.m2];
|
||||
}
|
||||
|
||||
recurse(root);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
function getResponsiveContainerClass(width) {
|
||||
if (width > 500) {
|
||||
return 'l';
|
||||
}
|
||||
|
||||
if (width > 200 && width <= 500) {
|
||||
return 'm';
|
||||
}
|
||||
|
||||
return 's';
|
||||
}
|
||||
|
||||
function getYOffset(width) {
|
||||
if (width > 500) {
|
||||
return ['0', '20', '40', '60'];
|
||||
}
|
||||
|
||||
if (width > 200 && width <= 500) {
|
||||
return ['0', '15', '30', '45'];
|
||||
}
|
||||
|
||||
return ['0', '10', '20', '30'];
|
||||
}
|
||||
|
||||
// Modified from http://bl.ocks.org/kerryrodden/7090426
|
||||
function Sunburst(element, props) {
|
||||
const container = d3.select(element);
|
||||
const {
|
||||
data,
|
||||
width,
|
||||
height,
|
||||
colorScheme,
|
||||
linearColorScheme,
|
||||
metrics,
|
||||
numberFormat,
|
||||
sliceId,
|
||||
} = props;
|
||||
const responsiveClass = getResponsiveContainerClass(width);
|
||||
const isSmallWidth = responsiveClass === 's';
|
||||
container.attr('class', `superset-legacy-chart-sunburst ${responsiveClass}`);
|
||||
// vars with shared scope within this function
|
||||
const margin = { top: 10, right: 5, bottom: 10, left: 5 };
|
||||
const containerWidth = width;
|
||||
const containerHeight = height;
|
||||
const breadcrumbHeight = containerHeight * 0.085;
|
||||
const visWidth = containerWidth - margin.left - margin.right;
|
||||
const visHeight =
|
||||
containerHeight - margin.top - margin.bottom - breadcrumbHeight;
|
||||
const radius = Math.min(visWidth, visHeight) / 2;
|
||||
|
||||
let colorByCategory = true; // color by category if primary/secondary metrics match
|
||||
let maxBreadcrumbs;
|
||||
let breadcrumbDims; // set based on data
|
||||
let totalSize; // total size of all segments; set after loading the data.
|
||||
let breadcrumbs;
|
||||
let vis;
|
||||
let arcs;
|
||||
let gMiddleText; // dom handles
|
||||
|
||||
const categoricalColorScale = CategoricalColorNamespace.getScale(colorScheme);
|
||||
let linearColorScale;
|
||||
|
||||
// Helper + path gen functions
|
||||
const partition = d3.layout
|
||||
.partition()
|
||||
.size([2 * Math.PI, radius * radius])
|
||||
.value(d => d.m1);
|
||||
|
||||
const arc = d3.svg
|
||||
.arc()
|
||||
.startAngle(d => d.x)
|
||||
.endAngle(d => d.x + d.dx)
|
||||
.innerRadius(d => Math.sqrt(d.y))
|
||||
.outerRadius(d => Math.sqrt(d.y + d.dy));
|
||||
|
||||
const formatNum = getNumberFormatter(
|
||||
numberFormat || NumberFormats.SI_3_DIGIT,
|
||||
);
|
||||
const formatPerc = getNumberFormatter(NumberFormats.PERCENT_3_POINT);
|
||||
|
||||
container.select('svg').remove();
|
||||
|
||||
const svg = container
|
||||
.append('svg:svg')
|
||||
.attr('width', containerWidth)
|
||||
.attr('height', containerHeight);
|
||||
|
||||
function createBreadcrumbs(firstRowData) {
|
||||
// -2 bc row contains 2x metrics, +extra for %label and buffer
|
||||
maxBreadcrumbs = firstRowData.length - 2 + 1;
|
||||
breadcrumbDims = {
|
||||
width: visWidth / maxBreadcrumbs,
|
||||
height: breadcrumbHeight * 0.8, // more margin
|
||||
spacing: 3,
|
||||
tipTailWidth: 10,
|
||||
};
|
||||
|
||||
breadcrumbs = svg
|
||||
.append('svg:g')
|
||||
.attr('class', 'breadcrumbs')
|
||||
.attr('transform', `translate(${margin.left},${margin.top})`);
|
||||
|
||||
breadcrumbs.append('svg:text').attr('class', 'end-label');
|
||||
}
|
||||
|
||||
// Generate a string that describes the points of a breadcrumb polygon.
|
||||
function breadcrumbPoints(d, i) {
|
||||
const points = [];
|
||||
if (isSmallWidth) {
|
||||
points.push('0,0');
|
||||
points.push(`${width},0`);
|
||||
points.push(`${width},0`);
|
||||
points.push(`${width},${breadcrumbDims.height}`);
|
||||
points.push(`0,${breadcrumbDims.height}`);
|
||||
if (i > 0) {
|
||||
// Leftmost breadcrumb; don't include 6th vertex.
|
||||
// points.push(`${breadcrumbDims.tipTailWidth},${breadcrumbDims.height / 2}`);
|
||||
}
|
||||
} else {
|
||||
points.push('0,0');
|
||||
points.push(`${breadcrumbDims.width},0`);
|
||||
points.push(
|
||||
`${breadcrumbDims.width + breadcrumbDims.tipTailWidth},${
|
||||
breadcrumbDims.height / 2
|
||||
}`,
|
||||
);
|
||||
points.push(`${breadcrumbDims.width},${breadcrumbDims.height}`);
|
||||
points.push(`0,${breadcrumbDims.height}`);
|
||||
if (i > 0) {
|
||||
// Leftmost breadcrumb; don't include 6th vertex.
|
||||
points.push(
|
||||
`${breadcrumbDims.tipTailWidth},${breadcrumbDims.height / 2}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return points.join(' ');
|
||||
}
|
||||
|
||||
function updateBreadcrumbs(sequenceArray, percentageString) {
|
||||
const breadcrumbWidth = isSmallWidth ? width : breadcrumbDims.width;
|
||||
const g = breadcrumbs
|
||||
.selectAll('g')
|
||||
.data(sequenceArray, d => d.name + d.depth);
|
||||
|
||||
// Add breadcrumb and label for entering nodes.
|
||||
const entering = g.enter().append('svg:g');
|
||||
|
||||
entering
|
||||
.append('svg:polygon')
|
||||
.attr('points', breadcrumbPoints)
|
||||
.style('fill', d =>
|
||||
colorByCategory
|
||||
? categoricalColorScale(d.name, sliceId)
|
||||
: linearColorScale(d.m2 / d.m1),
|
||||
);
|
||||
|
||||
entering
|
||||
.append('svg:text')
|
||||
.attr('x', (breadcrumbWidth + breadcrumbDims.tipTailWidth) / 2)
|
||||
.attr('y', breadcrumbDims.height / 4)
|
||||
.attr('dy', '0.35em')
|
||||
.style('fill', d => {
|
||||
// Make text white or black based on the lightness of the background
|
||||
const col = d3.hsl(
|
||||
colorByCategory
|
||||
? categoricalColorScale(d.name, sliceId)
|
||||
: linearColorScale(d.m2 / d.m1),
|
||||
);
|
||||
|
||||
return col.l < 0.5 ? 'white' : 'black';
|
||||
})
|
||||
.attr('class', 'step-label')
|
||||
.text(d => d.name.replace(/_/g, ' '))
|
||||
.call(wrapSvgText, breadcrumbWidth, breadcrumbDims.height / 2);
|
||||
|
||||
// Set position for entering and updating nodes.
|
||||
g.attr('transform', (d, i) => {
|
||||
if (isSmallWidth) {
|
||||
return `translate(0, ${
|
||||
i * (breadcrumbDims.height + breadcrumbDims.spacing)
|
||||
})`;
|
||||
}
|
||||
return `translate(${
|
||||
i * (breadcrumbDims.width + breadcrumbDims.spacing)
|
||||
}, 0)`;
|
||||
});
|
||||
|
||||
// Remove exiting nodes.
|
||||
g.exit().remove();
|
||||
|
||||
// Now move and update the percentage at the end.
|
||||
breadcrumbs
|
||||
.select('.end-label')
|
||||
.attr('x', () => {
|
||||
if (isSmallWidth) {
|
||||
return (breadcrumbWidth + breadcrumbDims.tipTailWidth) / 2;
|
||||
}
|
||||
|
||||
return (
|
||||
(sequenceArray.length + 0.5) *
|
||||
(breadcrumbDims.width + breadcrumbDims.spacing)
|
||||
);
|
||||
})
|
||||
.attr('y', () => {
|
||||
if (isSmallWidth) {
|
||||
return (sequenceArray.length + 1) * breadcrumbDims.height;
|
||||
}
|
||||
|
||||
return breadcrumbDims.height / 2;
|
||||
})
|
||||
.attr('dy', '0.35em')
|
||||
.text(percentageString);
|
||||
|
||||
// Make the breadcrumb trail visible, if it's hidden.
|
||||
breadcrumbs.style('visibility', null);
|
||||
}
|
||||
|
||||
// Fade all but the current sequence, and show it in the breadcrumb trail.
|
||||
function mouseenter(d) {
|
||||
const sequenceArray = getAncestors(d);
|
||||
const parentOfD = sequenceArray[sequenceArray.length - 2] || null;
|
||||
|
||||
const absolutePercentage = (d.m1 / totalSize).toPrecision(3);
|
||||
const conditionalPercentage = parentOfD
|
||||
? (d.m1 / parentOfD.m1).toPrecision(3)
|
||||
: null;
|
||||
|
||||
const absolutePercString = formatPerc(absolutePercentage);
|
||||
const conditionalPercString = parentOfD
|
||||
? formatPerc(conditionalPercentage)
|
||||
: '';
|
||||
|
||||
// 3 levels of text if inner-most level, 4 otherwise
|
||||
const yOffsets = getYOffset(width);
|
||||
let offsetIndex = 0;
|
||||
|
||||
// If metrics match, assume we are coloring by category
|
||||
const metricsMatch = Math.abs(d.m1 - d.m2) < 0.00001;
|
||||
|
||||
gMiddleText.selectAll('*').remove();
|
||||
|
||||
offsetIndex += 1;
|
||||
gMiddleText
|
||||
.append('text')
|
||||
.attr('class', 'path-abs-percent')
|
||||
.attr('y', yOffsets[offsetIndex])
|
||||
// eslint-disable-next-line prefer-template
|
||||
.text(absolutePercString + ' ' + t('of total'));
|
||||
|
||||
const OF_PARENT_TEXT = t('of parent');
|
||||
|
||||
if (conditionalPercString) {
|
||||
offsetIndex += 1;
|
||||
gMiddleText
|
||||
.append('text')
|
||||
.attr('class', 'path-cond-percent')
|
||||
.attr('y', yOffsets[offsetIndex])
|
||||
.text(`${conditionalPercString} ${OF_PARENT_TEXT}`);
|
||||
}
|
||||
|
||||
offsetIndex += 1;
|
||||
gMiddleText
|
||||
.append('text')
|
||||
.attr('class', 'path-metrics')
|
||||
.attr('y', yOffsets[offsetIndex])
|
||||
.text(
|
||||
`${metricLabel(metrics[0])}: ${formatNum(d.m1)}${
|
||||
metricsMatch ? '' : `, ${metricLabel(metrics[1])}: ${formatNum(d.m2)}`
|
||||
}`,
|
||||
);
|
||||
|
||||
offsetIndex += 1;
|
||||
gMiddleText
|
||||
.append('text')
|
||||
.attr('class', 'path-ratio')
|
||||
.attr('y', yOffsets[offsetIndex])
|
||||
.text(
|
||||
metricsMatch
|
||||
? ''
|
||||
: `${metricLabel(metrics[1])}/${metricLabel(
|
||||
metrics[0],
|
||||
)}: ${formatPerc(d.m2 / d.m1)}`,
|
||||
);
|
||||
|
||||
// Reset and fade all the segments.
|
||||
arcs
|
||||
.selectAll('path')
|
||||
.style('stroke-width', null)
|
||||
.style('stroke', null)
|
||||
.style('opacity', 0.3);
|
||||
|
||||
// Then highlight only those that are an ancestor of the current segment.
|
||||
arcs
|
||||
.selectAll('path')
|
||||
.filter(node => sequenceArray.includes(node))
|
||||
.style('opacity', 1)
|
||||
.style('stroke', '#aaa');
|
||||
|
||||
updateBreadcrumbs(sequenceArray, absolutePercString);
|
||||
}
|
||||
|
||||
// Restore everything to full opacity when moving off the visualization.
|
||||
function mouseleave() {
|
||||
// Hide the breadcrumb trail
|
||||
breadcrumbs.style('visibility', 'hidden');
|
||||
|
||||
gMiddleText.selectAll('*').remove();
|
||||
|
||||
// Deactivate all segments during transition.
|
||||
arcs.selectAll('path').on('mouseenter', null);
|
||||
|
||||
// Transition each segment to full opacity and then reactivate it.
|
||||
arcs
|
||||
.selectAll('path')
|
||||
.transition()
|
||||
.duration(200)
|
||||
.style('opacity', 1)
|
||||
.style('stroke', null)
|
||||
.style('stroke-width', null)
|
||||
.each('end', function end() {
|
||||
d3.select(this).on('mouseenter', mouseenter);
|
||||
});
|
||||
}
|
||||
|
||||
// Main function to draw and set up the visualization, once we have the data.
|
||||
function createVisualization(rows) {
|
||||
const root = buildHierarchy(rows);
|
||||
maxBreadcrumbs = rows[0].length - 2;
|
||||
vis = svg
|
||||
.append('svg:g')
|
||||
.attr('class', 'sunburst-vis')
|
||||
.attr(
|
||||
'transform',
|
||||
'translate(' +
|
||||
`${margin.left + visWidth / 2},` +
|
||||
`${
|
||||
margin.top +
|
||||
(isSmallWidth
|
||||
? breadcrumbHeight * maxBreadcrumbs
|
||||
: breadcrumbHeight) +
|
||||
visHeight / 2
|
||||
}` +
|
||||
')',
|
||||
)
|
||||
.on('mouseleave', mouseleave);
|
||||
|
||||
arcs = vis.append('svg:g').attr('id', 'arcs');
|
||||
|
||||
gMiddleText = vis.append('svg:g').attr('class', 'center-label');
|
||||
|
||||
// Bounding circle underneath the sunburst, to make it easier to detect
|
||||
// when the mouse leaves the parent g.
|
||||
arcs.append('svg:circle').attr('r', radius).style('opacity', 0);
|
||||
|
||||
// For efficiency, filter nodes to keep only those large enough to see.
|
||||
const nodes = partition.nodes(root).filter(d => d.dx > 0.005); // 0.005 radians = 0.29 degrees
|
||||
|
||||
if (metrics[0] !== metrics[1] && metrics[1]) {
|
||||
colorByCategory = false;
|
||||
const ext = d3.extent(nodes, d => d.m2 / d.m1);
|
||||
linearColorScale = getSequentialSchemeRegistry()
|
||||
.get(linearColorScheme)
|
||||
.createLinearScale(ext);
|
||||
}
|
||||
|
||||
arcs
|
||||
.selectAll('path')
|
||||
.data(nodes)
|
||||
.enter()
|
||||
.append('svg:path')
|
||||
.attr('display', d => (d.depth ? null : 'none'))
|
||||
.attr('d', arc)
|
||||
.attr('fill-rule', 'evenodd')
|
||||
.style('fill', d =>
|
||||
colorByCategory
|
||||
? categoricalColorScale(d.name, sliceId)
|
||||
: linearColorScale(d.m2 / d.m1),
|
||||
)
|
||||
.style('opacity', 1)
|
||||
.on('mouseenter', mouseenter);
|
||||
|
||||
// Get total size of the tree = value of root node from partition.
|
||||
totalSize = root.value;
|
||||
}
|
||||
createBreadcrumbs(data[0]);
|
||||
createVisualization(data);
|
||||
}
|
||||
|
||||
Sunburst.displayName = 'Sunburst';
|
||||
Sunburst.propTypes = propTypes;
|
||||
|
||||
export default Sunburst;
|
|
@ -1,108 +0,0 @@
|
|||
/**
|
||||
* 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/core';
|
||||
import {
|
||||
ControlPanelConfig,
|
||||
ControlPanelsContainerProps,
|
||||
getStandardizedControls,
|
||||
sections,
|
||||
} from '@superset-ui/chart-controls';
|
||||
|
||||
const config: ControlPanelConfig = {
|
||||
controlPanelSections: [
|
||||
sections.legacyRegularTime,
|
||||
{
|
||||
label: t('Query'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['groupby'],
|
||||
['metric'],
|
||||
['secondary_metric'],
|
||||
['adhoc_filters'],
|
||||
['row_limit'],
|
||||
[
|
||||
{
|
||||
name: 'sort_by_metric',
|
||||
config: {
|
||||
type: 'CheckboxControl',
|
||||
label: t('Sort by metric'),
|
||||
description: t(
|
||||
'Whether to sort results by the selected metric in descending order.',
|
||||
),
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [['color_scheme'], ['linear_color_scheme']],
|
||||
},
|
||||
],
|
||||
controlOverrides: {
|
||||
metric: {
|
||||
label: t('Primary Metric'),
|
||||
description: t(
|
||||
'The primary metric is used to define the arc segment sizes',
|
||||
),
|
||||
},
|
||||
secondary_metric: {
|
||||
label: t('Secondary Metric'),
|
||||
default: null,
|
||||
description: t(
|
||||
'[optional] this secondary metric is used to ' +
|
||||
'define the color as a ratio against the primary metric. ' +
|
||||
'When omitted, the color is categorical and based on labels',
|
||||
),
|
||||
},
|
||||
color_scheme: {
|
||||
description: t(
|
||||
'When only a primary metric is provided, a categorical color scale is used.',
|
||||
),
|
||||
visibility: ({ controls }: ControlPanelsContainerProps) =>
|
||||
Boolean(
|
||||
!controls?.secondary_metric?.value ||
|
||||
controls?.secondary_metric?.value === controls?.metric.value,
|
||||
),
|
||||
},
|
||||
linear_color_scheme: {
|
||||
description: t(
|
||||
'When a secondary metric is provided, a linear color scale is used.',
|
||||
),
|
||||
visibility: ({ controls }: ControlPanelsContainerProps) =>
|
||||
Boolean(
|
||||
controls?.secondary_metric?.value &&
|
||||
controls?.secondary_metric?.value !== controls?.metric.value,
|
||||
),
|
||||
},
|
||||
groupby: {
|
||||
label: t('Hierarchy'),
|
||||
description: t('This defines the level of the hierarchy'),
|
||||
},
|
||||
},
|
||||
formDataOverrides: formData => ({
|
||||
...formData,
|
||||
groupby: getStandardizedControls().popAllColumns(),
|
||||
metric: getStandardizedControls().shiftMetric(),
|
||||
secondary_metric: getStandardizedControls().shiftMetric(),
|
||||
}),
|
||||
};
|
||||
|
||||
export default config;
|
Binary file not shown.
Before Width: | Height: | Size: 139 KiB |
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
Binary file not shown.
Before Width: | Height: | Size: 50 KiB |
|
@ -1,47 +0,0 @@
|
|||
/**
|
||||
* 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, ChartMetadata, ChartPlugin } from '@superset-ui/core';
|
||||
import transformProps from './transformProps';
|
||||
import thumbnail from './images/thumbnail.png';
|
||||
import example from './images/example.png';
|
||||
import controlPanel from './controlPanel';
|
||||
|
||||
const metadata = new ChartMetadata({
|
||||
category: t('Part of a Whole'),
|
||||
credits: ['https://bl.ocks.org/kerryrodden/7090426'],
|
||||
description: t(
|
||||
'Uses circles to visualize the flow of data through different stages of a system. Hover over individual paths in the visualization to understand the stages a value took. Useful for multi-stage, multi-group visualizing funnels and pipelines.',
|
||||
),
|
||||
exampleGallery: [{ url: example }],
|
||||
name: t('Sunburst Chart'),
|
||||
tags: [t('Aesthetic'), t('Legacy'), t('Multi-Levels'), t('Proportional')],
|
||||
thumbnail,
|
||||
useLegacyApi: true,
|
||||
});
|
||||
|
||||
export default class SunburstChartPlugin extends ChartPlugin {
|
||||
constructor() {
|
||||
super({
|
||||
loadChart: () => import('./ReactSunburst'),
|
||||
metadata,
|
||||
transformProps,
|
||||
controlPanel,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
export default function transformProps(chartProps) {
|
||||
const { width, height, formData, queriesData, datasource } = chartProps;
|
||||
const { colorScheme, linearColorScheme, metric, secondaryMetric, sliceId } =
|
||||
formData;
|
||||
|
||||
const returnProps = {
|
||||
width,
|
||||
height,
|
||||
data: queriesData[0].data,
|
||||
colorScheme,
|
||||
linearColorScheme,
|
||||
metrics: [metric, secondaryMetric],
|
||||
sliceId,
|
||||
};
|
||||
|
||||
if (datasource && datasource.metrics) {
|
||||
const metricWithFormat = datasource.metrics.find(
|
||||
({ metric_name: metricName, d3format }) =>
|
||||
metricName === formData.metric && d3format,
|
||||
);
|
||||
if (metricWithFormat) {
|
||||
Object.assign(returnProps, { numberFormat: metricWithFormat.d3format });
|
||||
}
|
||||
}
|
||||
|
||||
return returnProps;
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
Utility function that takes a d3 svg:text selection and a max width, and splits the
|
||||
text's text across multiple tspan lines such that any given line does not exceed max width
|
||||
|
||||
If text does not span multiple lines AND adjustedY is passed,
|
||||
will set the text to the passed val
|
||||
*/
|
||||
import d3 from 'd3';
|
||||
|
||||
export default function wrapSvgText(text, width, adjustedY) {
|
||||
const lineHeight = 1;
|
||||
// ems
|
||||
text.each(function each() {
|
||||
const d3Text = d3.select(this);
|
||||
const words = d3Text.text().split(/\s+/);
|
||||
let line = [];
|
||||
let lineNumber = 0;
|
||||
const x = d3Text.attr('x');
|
||||
const y = d3Text.attr('y');
|
||||
const dy = parseFloat(d3Text.attr('dy'));
|
||||
let tspan = d3Text
|
||||
.text(null)
|
||||
.append('tspan')
|
||||
.attr('x', x)
|
||||
.attr('y', y)
|
||||
.attr('dy', `${dy}em`);
|
||||
|
||||
let didWrap = false;
|
||||
words.forEach(word => {
|
||||
line.push(word);
|
||||
tspan.text(line.join(' '));
|
||||
if (tspan.node().getComputedTextLength() > width) {
|
||||
lineNumber += 1;
|
||||
line.pop();
|
||||
// remove word that pushes over the limit
|
||||
tspan.text(line.join(' '));
|
||||
line = [word];
|
||||
tspan = d3Text
|
||||
.append('tspan')
|
||||
.attr('x', x)
|
||||
.attr('y', y)
|
||||
.attr('dy', `${lineNumber * lineHeight + dy}em`)
|
||||
.text(word);
|
||||
didWrap = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!didWrap && typeof adjustedY !== 'undefined') {
|
||||
tspan.attr('y', adjustedY);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"declarationDir": "lib",
|
||||
"outDir": "lib",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"exclude": [
|
||||
"lib",
|
||||
"test"
|
||||
],
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"types/**/*",
|
||||
"../../types/**/*"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../packages/superset-ui-chart-controls"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/superset-ui-core"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -43,7 +43,7 @@ export default class EchartsSunburstChartPlugin extends EchartsChartPlugin {
|
|||
'Uses circles to visualize the flow of data through different stages of a system. Hover over individual paths in the visualization to understand the stages a value took. Useful for multi-stage, multi-group visualizing funnels and pipelines.',
|
||||
),
|
||||
exampleGallery: [{ url: example1 }, { url: example2 }],
|
||||
name: t('Sunburst Chart v2'),
|
||||
name: t('Sunburst Chart'),
|
||||
tags: [
|
||||
t('ECharts'),
|
||||
t('Aesthetic'),
|
||||
|
|
|
@ -149,7 +149,7 @@ export const sliceEntitiesForDashboard = {
|
|||
slice_url: '/explore/?form_data=%7B%22slice_id%22%3A%20133%7D',
|
||||
slice_name: 'Rural Breakdown',
|
||||
form_data: {},
|
||||
viz_type: 'sunburst',
|
||||
viz_type: 'sunburst_v2',
|
||||
datasource: '2__table',
|
||||
description: null,
|
||||
description_markeddown: '',
|
||||
|
|
|
@ -37,7 +37,7 @@ jest.mock('src/components/Dropdown', () => {
|
|||
};
|
||||
});
|
||||
|
||||
const createProps = (viz_type = 'sunburst') =>
|
||||
const createProps = (viz_type = 'sunburst_v2') =>
|
||||
({
|
||||
addDangerToast: jest.fn(),
|
||||
addSuccessToast: jest.fn(),
|
||||
|
@ -59,7 +59,9 @@ const createProps = (viz_type = 'sunburst') =>
|
|||
adhoc_filters: [],
|
||||
color_scheme: 'supersetColors',
|
||||
datasource: '58__table',
|
||||
groupby: ['product_category', 'clinical_stage'],
|
||||
...(viz_type === 'sunburst_v2'
|
||||
? { columns: ['product_category', 'clinical_stage'] }
|
||||
: { groupby: ['product_category', 'clinical_stage'] }),
|
||||
linear_color_scheme: 'schemeYlOrBr',
|
||||
metric: 'count',
|
||||
queryFields: {
|
||||
|
@ -93,7 +95,7 @@ const createProps = (viz_type = 'sunburst') =>
|
|||
chartStatus: 'rendered',
|
||||
showControls: true,
|
||||
supersetCanShare: true,
|
||||
formData: { slice_id: 1, datasource: '58__table', viz_type: 'sunburst' },
|
||||
formData: { slice_id: 1, datasource: '58__table', viz_type: 'sunburst_v2' },
|
||||
exploreUrl: '/explore',
|
||||
} as SliceHeaderControlsProps);
|
||||
|
||||
|
|
|
@ -93,7 +93,6 @@ const DEFAULT_ORDER = [
|
|||
'deck_screengrid',
|
||||
'treemap_v2',
|
||||
'box_plot',
|
||||
'sunburst',
|
||||
'sankey',
|
||||
'word_cloud',
|
||||
'mapbox',
|
||||
|
|
|
@ -30,7 +30,6 @@ import ParallelCoordinatesChartPlugin from '@superset-ui/legacy-plugin-chart-par
|
|||
import PartitionChartPlugin from '@superset-ui/legacy-plugin-chart-partition';
|
||||
import RoseChartPlugin from '@superset-ui/legacy-plugin-chart-rose';
|
||||
import SankeyChartPlugin from '@superset-ui/legacy-plugin-chart-sankey';
|
||||
import SunburstChartPlugin from '@superset-ui/legacy-plugin-chart-sunburst';
|
||||
import TableChartPlugin from '@superset-ui/plugin-chart-table';
|
||||
import { WordCloudChartPlugin } from '@superset-ui/plugin-chart-word-cloud';
|
||||
import WorldMapChartPlugin from '@superset-ui/legacy-plugin-chart-world-map';
|
||||
|
@ -127,7 +126,6 @@ export default class MainPreset extends Preset {
|
|||
new PivotTableChartPluginV2().configure({ key: 'pivot_table_v2' }),
|
||||
new RoseChartPlugin().configure({ key: 'rose' }),
|
||||
new SankeyChartPlugin().configure({ key: 'sankey' }),
|
||||
new SunburstChartPlugin().configure({ key: 'sunburst' }),
|
||||
new TableChartPlugin().configure({ key: 'table' }),
|
||||
new TimePivotChartPlugin().configure({ key: 'time_pivot' }),
|
||||
new TimeTableChartPlugin().configure({ key: 'time_table' }),
|
||||
|
|
|
@ -15,14 +15,14 @@
|
|||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
slice_name: Vaccine Candidates per Country & Stage
|
||||
viz_type: sunburst
|
||||
viz_type: sunburst_v2
|
||||
params:
|
||||
adhoc_filters: []
|
||||
color_scheme: supersetColors
|
||||
datasource: 69__table
|
||||
groupby:
|
||||
- product_category
|
||||
- clinical_stage
|
||||
columns:
|
||||
- product_category
|
||||
- clinical_stage
|
||||
linear_color_scheme: schemeYlOrBr
|
||||
metric: count
|
||||
queryFields:
|
||||
|
@ -33,7 +33,7 @@ params:
|
|||
slice_id: 3964
|
||||
time_range: No filter
|
||||
url_params: {}
|
||||
viz_type: sunburst
|
||||
viz_type: sunburst_v2
|
||||
cache_timeout: null
|
||||
uuid: f69c556f-15fe-4a82-a8bb-69d5b6954123
|
||||
version: 1.0.0
|
||||
|
|
|
@ -397,12 +397,12 @@ def create_slices(tbl: SqlaTable) -> list[Slice]:
|
|||
Slice(
|
||||
**slice_kwargs,
|
||||
slice_name="Sunburst Chart",
|
||||
viz_type="sunburst",
|
||||
viz_type="sunburst_v2",
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
viz_type="sunburst",
|
||||
viz_type="sunburst_v2",
|
||||
metric="sum__num",
|
||||
groupby=["gender", "state"],
|
||||
columns=["gender", "state"],
|
||||
),
|
||||
),
|
||||
Slice(
|
||||
|
|
|
@ -266,13 +266,13 @@ def create_slices(tbl: BaseDatasource) -> list[Slice]:
|
|||
),
|
||||
Slice(
|
||||
slice_name="Rural Breakdown",
|
||||
viz_type="sunburst",
|
||||
viz_type="sunburst_v2",
|
||||
datasource_type=DatasourceType.TABLE,
|
||||
datasource_id=tbl.id,
|
||||
params=get_slice_json(
|
||||
defaults,
|
||||
viz_type="sunburst",
|
||||
groupby=["region", "country_name"],
|
||||
viz_type="sunburst_v2",
|
||||
columns=["region", "country_name"],
|
||||
since="2011-01-01",
|
||||
until="2011-01-02",
|
||||
metric=metric,
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
# 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.
|
||||
"""migrate-sunburst-chart
|
||||
|
||||
Revision ID: a32e0c4d8646
|
||||
Revises: 59a1450b3c10
|
||||
Create Date: 2023-12-22 14:41:43.638321
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "a32e0c4d8646"
|
||||
down_revision = "59a1450b3c10"
|
||||
|
||||
from alembic import op
|
||||
|
||||
from superset import db
|
||||
from superset.migrations.shared.migrate_viz import MigrateSunburst
|
||||
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind)
|
||||
MigrateSunburst.upgrade(session)
|
||||
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind)
|
||||
MigrateSunburst.downgrade(session)
|
|
@ -1352,55 +1352,6 @@ class DistributionBarViz(BaseViz):
|
|||
return chart_data
|
||||
|
||||
|
||||
class SunburstViz(BaseViz):
|
||||
|
||||
"""A multi level sunburst chart"""
|
||||
|
||||
viz_type = "sunburst"
|
||||
verbose_name = _("Sunburst")
|
||||
is_timeseries = False
|
||||
credits = (
|
||||
"Kerry Rodden "
|
||||
'@<a href="https://bl.ocks.org/kerryrodden/7090426">bl.ocks.org</a>'
|
||||
)
|
||||
|
||||
@deprecated(deprecated_in="3.0")
|
||||
def get_data(self, df: pd.DataFrame) -> VizData:
|
||||
if df.empty:
|
||||
return None
|
||||
form_data = copy.deepcopy(self.form_data)
|
||||
cols = get_column_names(form_data.get("groupby"))
|
||||
cols.extend(["m1", "m2"])
|
||||
metric = utils.get_metric_name(form_data["metric"])
|
||||
secondary_metric = (
|
||||
utils.get_metric_name(form_data["secondary_metric"])
|
||||
if form_data.get("secondary_metric")
|
||||
else None
|
||||
)
|
||||
if metric == secondary_metric or secondary_metric is None:
|
||||
df.rename(columns={df.columns[-1]: "m1"}, inplace=True)
|
||||
df["m2"] = df["m1"]
|
||||
else:
|
||||
df.rename(columns={df.columns[-2]: "m1"}, inplace=True)
|
||||
df.rename(columns={df.columns[-1]: "m2"}, inplace=True)
|
||||
|
||||
# Re-order the columns as the query result set column ordering may differ from
|
||||
# that listed in the hierarchy.
|
||||
df = df[cols]
|
||||
return df.to_numpy().tolist()
|
||||
|
||||
@deprecated(deprecated_in="3.0")
|
||||
def query_obj(self) -> QueryObjectDict:
|
||||
query_obj = super().query_obj()
|
||||
query_obj["metrics"] = [self.form_data["metric"]]
|
||||
secondary_metric = self.form_data.get("secondary_metric")
|
||||
if secondary_metric and secondary_metric != self.form_data["metric"]:
|
||||
query_obj["metrics"].append(secondary_metric)
|
||||
if self.form_data.get("sort_by_metric", False):
|
||||
query_obj["orderby"] = [(query_obj["metrics"][0], False)]
|
||||
return query_obj
|
||||
|
||||
|
||||
class SankeyViz(BaseViz):
|
||||
|
||||
"""A Sankey diagram that requires a parent-child dataset"""
|
||||
|
|
Loading…
Reference in New Issue