refactor: Migrates legacy Sunburst charts to ECharts and removes legacy code (#26350)

This commit is contained in:
Michael S. Molina 2024-01-18 11:59:08 -03:00 committed by GitHub
parent cf20b3439c
commit 4d9144eca5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 82 additions and 1229 deletions

View File

@ -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' },

View File

@ -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', () => {

View File

@ -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": {

View File

@ -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",

View File

@ -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: [],

View File

@ -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": "*",

View File

@ -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>
);

View File

@ -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],
];

View File

@ -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];

View File

@ -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

View File

@ -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: {...},
}]}
/>
```

View File

@ -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"
}
}

View File

@ -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};
}
`}
`;

View File

@ -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;

View File

@ -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

View File

@ -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,
});
}
}

View File

@ -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;
}

View File

@ -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);
}
});
}

View File

@ -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"
}
]
}

View File

@ -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'),

View File

@ -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: '',

View File

@ -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);

View File

@ -93,7 +93,6 @@ const DEFAULT_ORDER = [
'deck_screengrid',
'treemap_v2',
'box_plot',
'sunburst',
'sankey',
'word_cloud',
'mapbox',

View File

@ -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' }),

View File

@ -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

View File

@ -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(

View File

@ -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,

View File

@ -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)

View File

@ -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"""