From 08a7259c7b56962058428158e7b379ef3e04d1b4 Mon Sep 17 00:00:00 2001 From: Raphael Benitte Date: Thu, 21 Apr 2016 09:02:39 +0900 Subject: [PATCH] feat(bubble): move Bubble legends in a dedicated component --- src/components/layouts/Bubble.js | 71 +++++++++++++++---------- src/components/layouts/BubbleLegends.js | 71 +++++++++++++++++++++++++ src/index.js | 1 + 3 files changed, 116 insertions(+), 27 deletions(-) create mode 100644 src/components/layouts/BubbleLegends.js diff --git a/src/components/layouts/Bubble.js b/src/components/layouts/Bubble.js index 54799ec1d..d57283d55 100644 --- a/src/components/layouts/Bubble.js +++ b/src/components/layouts/Bubble.js @@ -5,16 +5,15 @@ import Dimensions from 'react-dimensions'; import Nivo from '../../Nivo'; import { margin as marginPropType } from '../../PropTypes'; import { flatten } from '../../DataUtils'; -import { getColorStyleObject, getColorRange } from '../../ColorUtils'; +import { getColorRange } from '../../ColorUtils'; class Bubble extends Component { renderD3(nextProps) { const { root, - mode, - valueAccessor, labelFn, containerWidth, containerHeight, + padding, colors, transitionDuration, transitionEasing } = nextProps; @@ -38,43 +37,35 @@ class Bubble extends Component { const bubble = d3.layout.pack() .sort(null) .size([width, height]) - .padding(1) - ; - - let nodes = wrapper.selectAll('.bubble_node') - .data(bubble.nodes(flatten(root)) - .filter(d => !d.children)) + .padding(padding) ; const color = getColorRange(colors); + const flattened = flatten(root); + flattened.children.forEach(child => { + child.color = color(child.packageName); + }); + + const bubbled = bubble.nodes(flattened).filter(d => !d.children); + const nodes = wrapper.selectAll('.bubble_node').data(bubbled); + nodes - .enter().append('g') + .enter().append('circle') .attr('class', 'bubble_node') - .attr('transform', d => `translate(${d.x},${d.y})`) - .append('circle') .attr('r', 2) .style('fill', d => color(d.packageName)) + .attr('transform', d => `translate(${d.x},${d.y})`) ; nodes .transition() .duration(transitionDuration) .ease(transitionEasing) + .attr('r', d => d.r) .attr('transform', d => `translate(${d.x},${d.y})`) ; - nodes.each(function (d) { - const el = d3.select(this); - - el.select('circle') - .transition() - .duration(transitionDuration) - .ease(transitionEasing) - .attr('r', d => d.r) - ; - }); - nodes.exit() .transition() .duration(transitionDuration) @@ -82,6 +73,21 @@ class Bubble extends Component { .attr('transform', `translate(${width / 2},${height / 2})`) .remove() ; + + const bubbleContext = { + element: wrapper, + width, + height, + rawData: root, + flatData: flattened, + data: bubbled, + transitionDuration, + transitionEasing + }; + + this.legends.forEach(legend => { + legend(bubbleContext); + }); } shouldComponentUpdate(nextProps) { @@ -95,6 +101,19 @@ class Bubble extends Component { } componentWillMount() { + const { children } = this.props; + + const legends = []; + + React.Children.forEach(children, element => { + if (React.isValidElement(element)) { + if (element.type.createBubbleLegendsFromReactElement) { + legends.push(element.type.createBubbleLegendsFromReactElement(element)); + } + } + }); + + this.legends = legends; } render() { @@ -113,8 +132,7 @@ Bubble.propTypes = { containerHeight: number.isRequired, margin: marginPropType, root: object.isRequired, - valueAccessor: func.isRequired, - labelFn: func.isRequired, + padding: number.isRequired, colors: any.isRequired, transitionDuration: number.isRequired, transitionEasing: string.isRequired @@ -122,8 +140,7 @@ Bubble.propTypes = { Bubble.defaultProps = { margin: Nivo.defaults.margin, - valueAccessor: d => d.size, - labelFn: d => d.name, + padding: 1, colors: Nivo.defaults.colorRange, transitionDuration: Nivo.defaults.transitionDuration, transitionEasing: Nivo.defaults.transitionEasing diff --git a/src/components/layouts/BubbleLegends.js b/src/components/layouts/BubbleLegends.js new file mode 100644 index 000000000..ff025ebd0 --- /dev/null +++ b/src/components/layouts/BubbleLegends.js @@ -0,0 +1,71 @@ +import React, { Component, PropTypes } from 'react'; +import invariant from 'invariant'; +import Nivo from '../../Nivo'; +import { getColorStyleObject } from '../../ColorUtils'; + + +class BubbleLegends extends Component { + static createBubbleLegendsFromReactElement(element) { + const { props } = element; + + const { textColor, labelAccessor, skipRadius } = props; + + const textColorStyle = getColorStyleObject(textColor, 'fill'); + + return ({ element, data, width, height, transitionDuration, transitionEasing }) => { + + if (skipRadius > 0) { + data = data.filter(d => d.r >= skipRadius); + } + + const legends = element.selectAll('.bubble_legend').data(data); + + legends.enter().append('text') + .attr('class', 'bubble_legend') + .style('text-anchor', 'middle') + .style(textColorStyle) + .style('opacity', 0) + .text(labelAccessor) + .attr('transform', d => `translate(${d.x},${d.y})`) + ; + + legends + .text(labelAccessor) + .transition() + .duration(transitionDuration) + .ease(transitionEasing) + .style(textColorStyle) + .style('opacity', 1) + .attr('transform', d => `translate(${d.x},${d.y})`) + ; + + legends.exit() + .remove() + ; + }; + } + + render() { + invariant( + false, + ' element is for Bubble configuration only and should not be rendered' + ); + } +} + +const { number, func, any } = PropTypes; + +BubbleLegends.propTypes = { + labelAccessor: func.isRequired, + textColor: any.isRequired, + skipRadius: number.isRequired +}; + +BubbleLegends.defaultProps = { + labelAccessor: d => d.name, + textColor: 'none', + skipRadius: 0 +}; + + +export default BubbleLegends; diff --git a/src/index.js b/src/index.js index 3c6a96411..1da1726a6 100644 --- a/src/index.js +++ b/src/index.js @@ -13,3 +13,4 @@ export Stack from './components/layouts/Stack'; export RadialStack from './components/layouts/RadialStack'; export TreeMap from './components/layouts/TreeMap'; export Bubble from './components/layouts/Bubble'; +export BubbleLegends from './components/layouts/BubbleLegends';