Skip to content

Commit

Permalink
feat: change font size on responsive for sankey and sunburst chart (#977
Browse files Browse the repository at this point in the history
)

* feat: change font size on responsive

* remove logs

* Sunburst

* WIP

* Sankey label hide

* Format correct object

* add styled components

* Replace document with container

* Return overlapping elements

* Drag

* Fix lint

* Fix test & make tooltip absoliute based on cursor position

* Update storybook for charts

* Resizable in separate page

* Fix responsive class for sunburst

* type
  • Loading branch information
maloun96 authored and zhaoyongjie committed Nov 26, 2021
1 parent e67064e commit 2625698
Show file tree
Hide file tree
Showing 9 changed files with 332 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import React from 'react';
import { SuperChart } from '@superset-ui/core';
import SankeyChartPlugin from '@superset-ui/legacy-plugin-chart-sankey';
import ResizableChartDemo from '../../../shared/components/ResizableChartDemo';
import data from './data';

new SankeyChartPlugin().configure({ key: 'sankey' }).register();
Expand All @@ -21,3 +22,19 @@ export const basic = () => (
}}
/>
);

export const resizable = () => (
<ResizableChartDemo>
{({ width, height }) => (
<SuperChart
chartType="sankey"
width={width}
height={height}
queriesData={[{ data }]}
formData={{
colorScheme: 'd3Category10',
}}
/>
)}
</ResizableChartDemo>
);
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
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();
Expand All @@ -23,3 +24,21 @@ export const basic = () => (
}}
/>
);

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>
);
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default styled(SankeyComponent)`
text {
pointer-events: none;
text-shadow: 0 1px 0 #fff;
font-size: ${({ fontSize }) => fontSize}em;
}
}
.link {
Expand All @@ -54,15 +55,18 @@ export default styled(SankeyComponent)`
stroke-opacity: 0.5;
}
}
.opacity-0 {
opacity: 0;
}
}
.superset-legacy-chart-sankey-tooltip {
.sankey-tooltip {
position: absolute;
width: auto;
background: #ddd;
padding: 10px;
font-size: ${({ theme }) => theme.typography.sizes.s};
font-size: ${({ fontSize }) => fontSize}em;
font-weight: ${({ theme }) => theme.typography.weights.light};
color: #333;
color: #000;
border: 1px solid #fff;
text-align: center;
pointer-events: none;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import d3 from 'd3';
import PropTypes from 'prop-types';
import { sankey as d3Sankey } from 'd3-sankey';
import { getNumberFormatter, NumberFormats, CategoricalColorNamespace } from '@superset-ui/core';
import { getOverlappingElements } from './utils';

const propTypes = {
data: PropTypes.arrayOf(
Expand All @@ -40,9 +41,8 @@ const formatNumber = getNumberFormatter(NumberFormats.FLOAT);

function Sankey(element, props) {
const { data, width, height, colorScheme } = props;

const div = d3.select(element);
div.classed('superset-legacy-chart-sankey', true);
div.classed(`superset-legacy-chart-sankey`, true);
const margin = {
top: 5,
right: 5,
Expand All @@ -53,15 +53,14 @@ function Sankey(element, props) {
const innerHeight = height - margin.top - margin.bottom;

div.selectAll('*').remove();
const tooltip = div.append('div').attr('class', 'sankey-tooltip').style('opacity', 0);
const svg = div
.append('svg')
.attr('width', innerWidth + margin.left + margin.right)
.attr('height', innerHeight + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);

const tooltip = div.append('div').attr('class', 'sankey-tooltip').style('opacity', 0);

const colorFn = CategoricalColorNamespace.getScale(colorScheme);

const sankey = d3Sankey().nodeWidth(15).nodePadding(10).size([innerWidth, innerHeight]);
Expand Down Expand Up @@ -119,6 +118,7 @@ function Sankey(element, props) {
.duration(200)
.style('left', `${d3.event.offsetX + 10}px`)
.style('top', `${d3.event.offsetY + 10}px`)
.style('position', 'absolute')
.style('opacity', 0.95);
}

Expand Down Expand Up @@ -148,6 +148,23 @@ function Sankey(element, props) {
link.attr('d', path);
}

function checkVisibility() {
const elements = div.selectAll('.node')[0] ?? [];
const overlappingElements = getOverlappingElements(elements);

elements.forEach(el => {
const text = el.getElementsByTagName('text')[0];

if (text) {
if (overlappingElements.includes(el)) {
text.classList.add('opacity-0');
} else {
text.classList.remove('opacity-0');
}
}
});
}

const node = svg
.append('g')
.selectAll('.node')
Expand All @@ -163,7 +180,8 @@ function Sankey(element, props) {
.on('dragstart', function dragStart() {
this.parentNode.append(this);
})
.on('drag', dragmove),
.on('drag', dragmove)
.on('dragend', checkVisibility),
);
const minRectHeight = 5;
node
Expand All @@ -188,9 +206,12 @@ function Sankey(element, props) {
.attr('text-anchor', 'end')
.attr('transform', null)
.text(d => d.name)
.attr('class', 'opacity-0')
.filter(d => d.x < innerWidth / 2)
.attr('x', 6 + sankey.nodeWidth())
.attr('text-anchor', 'start');

checkVisibility();
}

Sankey.displayName = 'Sankey';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* 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 { getOverlappingElements, isOverlapping } from '../utils';

const overlapRects = [
{
x: 10,
y: 10,
width: 10,
height: 10,
},
{
x: 12,
y: 12,
width: 12,
height: 12,
},
{
x: 32,
y: 32,
width: 32,
height: 32,
},
];

const notOverlapRects = [
{
x: 10,
y: 10,
width: 10,
height: 10,
},
{
x: 24,
y: 15,
width: 15,
height: 15,
},
{
x: 32,
y: 32,
width: 32,
height: 32,
},
];

const createSVGs = objects =>
objects.map(data => {
const el = document.createElementNS('http://www.w3.org/2000/svg', 'text');
el.getBoundingClientRect = jest.fn(() => data);

return el;
});

// https://www.khanacademy.org/computer-programming/rectx-y-width-height-radius/839496660
describe('legacy-plugin-chart-sankey/utils', () => {
it('isOverlapping to be truthy', () => {
const [rect1, rect2] = overlapRects;
expect(isOverlapping(rect1, rect2)).toBeTruthy();
});

it('isOverlapping to be falsy', () => {
const [rect1, rect2] = notOverlapRects;
expect(isOverlapping(rect1, rect2)).toBeFalsy();
});

it('getOverlappingElements to be truthy', () => {
const elements = createSVGs(overlapRects);
expect(getOverlappingElements(elements).length).toBe(2);
});

it('getOverlappingElements to be falsy', () => {
const elements = createSVGs(notOverlapRects);
expect(getOverlappingElements(elements).length).toBe(0);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
import { getLabelFontSize } from './utils';

export default function transformProps(chartProps) {
const { width, height, formData, queriesData } = chartProps;
const { colorScheme } = formData;
Expand All @@ -25,5 +27,6 @@ export default function transformProps(chartProps) {
height,
data: queriesData[0].data,
colorScheme,
fontSize: getLabelFontSize(width),
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* 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.
*/
type Rect = {
x: number;
y: number;
width: number;
height: number;
};

export function getLabelFontSize(width: number): number {
if (width > 550) {
return 0.8;
}

if (width > 400 && width <= 550) {
return 0.55;
}

return 0.45;
}

export const isOverlapping = (rect1: Rect, rect2: Rect): boolean => {
const { x: x1, y: y1, width: width1, height: height1 } = rect1;
const { x: x2, y: y2, width: width2, height: height2 } = rect2;

return !(x1 > x2 + width2 || x1 + width1 < x2 || y1 > y2 + height2 || y1 + height1 < y2);
};

export const getRectangle = (element: SVGElement, offset = 0): Rect => {
const { x, y, width, height } = element.getBoundingClientRect();

return {
x,
y: y + offset,
width,
height: height - offset * 2,
};
};

export const getOverlappingElements = (elements: SVGElement[]): SVGElement[] => {
const overlappingElements: SVGElement[] = [];

elements.forEach((e1, index1) => {
const rect1: Rect = getRectangle(e1, 1);

elements.forEach((e2, index2) => {
if (index2 <= index1) return;
const rect2: Rect = getRectangle(e2, 1);

if (isOverlapping(rect1, rect2)) {
overlappingElements.push(elements[index2]);
overlappingElements.push(elements[index1]);
}
});
});

return overlappingElements;
};

0 comments on commit 2625698

Please sign in to comment.