Skip to content

Commit

Permalink
feat(circle-packing): add support for custom circle component to SVG …
Browse files Browse the repository at this point in the history
…and HTML implementations
  • Loading branch information
plouc authored and wyze committed Apr 26, 2021
1 parent d917057 commit 6f8a4ca
Show file tree
Hide file tree
Showing 13 changed files with 184 additions and 83 deletions.
6 changes: 4 additions & 2 deletions packages/circle-packing/src/CirclePacking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ const InnerCirclePacking = <RawDatum,>({
>,
colorBy = defaultProps.colorBy,
childColor = defaultProps.childColor as InheritedColorConfig<ComputedDatum<RawDatum>>,
circleComponent = CircleSvg,
enableLabels = defaultProps.enableLabels,
label = defaultProps.label,
labelsFilter,
labelsSkipRadius = defaultProps.labelsSkipRadius,
labelsTextColor = defaultProps.labelsTextColor as InheritedColorConfig<ComputedDatum<RawDatum>>,
labelsComponent = LabelSvg,
layers = defaultProps.layers,
isInteractive,
onMouseEnter,
Expand Down Expand Up @@ -87,7 +89,7 @@ const InnerCirclePacking = <RawDatum,>({
onMouseMove={onMouseMove}
onMouseLeave={onMouseLeave}
onClick={onClick}
component={CircleSvg}
component={circleComponent}
tooltip={tooltip}
/>
)
Expand All @@ -102,7 +104,7 @@ const InnerCirclePacking = <RawDatum,>({
filter={labelsFilter}
skipRadius={labelsSkipRadius}
textColor={labelsTextColor}
component={LabelSvg}
component={labelsComponent}
/>
)
}
Expand Down
6 changes: 4 additions & 2 deletions packages/circle-packing/src/CirclePackingHtml.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ export const InnerCirclePackingHtml = <RawDatum,>({
>,
colorBy = defaultProps.colorBy,
childColor = defaultProps.childColor as InheritedColorConfig<ComputedDatum<RawDatum>>,
circleComponent = CircleHtml,
enableLabels = defaultProps.enableLabels,
label = defaultProps.label,
labelsFilter,
labelsSkipRadius = defaultProps.labelsSkipRadius,
labelsTextColor = defaultProps.labelsTextColor as InheritedColorConfig<ComputedDatum<RawDatum>>,
labelsComponent = LabelHtml,
layers = defaultProps.layers,
isInteractive,
onMouseEnter,
Expand Down Expand Up @@ -81,7 +83,7 @@ export const InnerCirclePackingHtml = <RawDatum,>({
onMouseMove={onMouseMove}
onMouseLeave={onMouseLeave}
onClick={onClick}
component={CircleHtml}
component={circleComponent}
tooltip={tooltip}
/>
)
Expand All @@ -96,7 +98,7 @@ export const InnerCirclePackingHtml = <RawDatum,>({
filter={labelsFilter}
skipRadius={labelsSkipRadius}
textColor={labelsTextColor}
component={LabelHtml}
component={labelsComponent}
/>
)
}
Expand Down
7 changes: 6 additions & 1 deletion packages/circle-packing/src/ResponsiveCirclePacking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ import { ResponsiveWrapper } from '@nivo/core'
import { CirclePacking } from './CirclePacking'
import { CirclePackSvgProps } from './types'

type ResponsiveCirclePackingProps<RawDatum> = Partial<
Omit<CirclePackingSvgProps<RawDatum>, 'data' | 'width' | 'height'>
> &
Pick<CirclePackingSvgProps<RawDatum>, 'data'>

export const ResponsiveCirclePacking = <RawDatum,>(
props: Omit<CirclePackSvgProps<RawDatum>, 'width' | 'height'>
props: ResponsiveCirclePackingProps<RawDatum>
) => (
<ResponsiveWrapper>
{({ width, height }: { width: number; height: number }) => (
Expand Down
8 changes: 6 additions & 2 deletions packages/circle-packing/src/ResponsiveCirclePackingCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import { ResponsiveWrapper } from '@nivo/core'
import { CirclePackingCanvasProps } from './types'
import { CirclePackingCanvas } from './CirclePackingCanvas'

type ResponsiveCirclePackingCanvasProps<RawDatum> = Partial<
Omit<CirclePackingCanvasProps<RawDatum>, 'data' | 'width' | 'height'>
> &
Pick<CirclePackingCanvasProps<RawDatum>, 'data'>

export const ResponsiveCirclePackingCanvas = <RawDatum,>(
props: Partial<Omit<CirclePackingCanvasProps<RawDatum>, 'data' | 'width' | 'height'>> &
Pick<CirclePackingCanvasProps<RawDatum>, 'data'>
props: ResponsiveCirclePackingCanvasProps<RawDatum>
) => (
<ResponsiveWrapper>
{({ width, height }: { width: number; height: number }) => (
Expand Down
19 changes: 19 additions & 0 deletions packages/circle-packing/src/ResponsiveCirclePackingHtml.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react'
import { ResponsiveWrapper } from '@nivo/core'
import { CirclePackingHtmlProps } from './types'
import { CirclePackingHtml } from './CirclePackingHtml'

type ResponsiveCirclePackingHtmlProps<RawDatum> = Partial<
Omit<CirclePackingHtmlProps<RawDatum>, 'data' | 'width' | 'height'>
> &
Pick<CirclePackingHtmlProps<RawDatum>, 'data'>

export const ResponsiveCirclePackingHtml = <RawDatum,>(
props: ResponsiveCirclePackingHtmlProps<RawDatum>
) => (
<ResponsiveWrapper>
{({ width, height }: { width: number; height: number }) => (
<CirclePackingHtml<RawDatum> width={width} height={height} {...props} />
)}
</ResponsiveWrapper>
)
2 changes: 2 additions & 0 deletions packages/circle-packing/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,14 @@ export interface CirclePackingCommonProps<RawDatum> {
childColor: InheritedColorConfig<ComputedDatum<RawDatum>>
borderWidth: number
borderColor: InheritedColorConfig<ComputedDatum<RawDatum>>
circleComponent: CircleComponent<RawDatum>

enableLabels: boolean
label: PropertyAccessor<ComputedDatum<RawDatum>, string | number>
labelsFilter?: (label: ComputedLabel<RawDatum>) => boolean
labelsSkipRadius: number
labelsTextColor: InheritedColorConfig<ComputedDatum<RawDatum>>
labelsComponent: LabelComponent<RawDatum>

layers: CirclePackingLayer<RawDatum>[]

Expand Down
175 changes: 104 additions & 71 deletions packages/circle-packing/tests/CirclePacking.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,79 +18,112 @@ const testCases = [

testCases.forEach(testCase => {
describe(testCase.Chart, () => {
it('should render as much node as items', () => {
const wrapper = mount(
<testCase.Chart<SampleDatum> width={600} height={600} data={sampleData} />
)

expect(wrapper.find(testCase.circle).length).toBe(11)
})

it(`should skip parent nodes if 'leavesOnly' is 'true'`, () => {
const wrapper = mount(
<testCase.Chart<SampleDatum>
width={600}
height={600}
data={sampleData}
leavesOnly={true}
/>
)

expect(wrapper.find(testCase.circle).length).toBe(7)
})

it(`should render as much labels as nodes if 'enableLabels' is 'true'`, () => {
const wrapper = mount(
<testCase.Chart<SampleDatum>
width={600}
height={600}
data={sampleData}
enableLabels={true}
/>
)

expect(wrapper.find(testCase.label).length).toBe(11)
})

it(`should render no label if 'enableLabels' is 'false'`, () => {
const wrapper = mount(
<testCase.Chart<SampleDatum>
width={600}
height={600}
data={sampleData}
enableLabels={false}
/>
)

expect(wrapper.find(testCase.label).length).toBe(0)
})

it(`should allow to skip labels using 'labelsSkipRadius' if radius is lower than given value`, () => {
const wrapper = mount(
<testCase.Chart<SampleDatum>
width={600}
height={600}
data={sampleData}
enableLabels={true}
labelsSkipRadius={24}
/>
)

expect(wrapper.find(testCase.label).length).toBe(10)
describe('circles', () => {
it('should render as much circles as items', () => {
const wrapper = mount(
<testCase.Chart<SampleDatum> width={600} height={600} data={sampleData} />
)

expect(wrapper.find(testCase.circle).length).toBe(11)
})

it(`should only render leaf nodes if 'leavesOnly' is 'true'`, () => {
const wrapper = mount(
<testCase.Chart<SampleDatum>
width={600}
height={600}
data={sampleData}
leavesOnly={true}
/>
)

expect(wrapper.find(testCase.circle).length).toBe(7)
})

it('should support a custom circle component', () => {
const CustomCircle = () => <span />
const wrapper = mount(
<testCase.Chart<SampleDatum>
width={600}
height={600}
data={sampleData}
circleComponent={CustomCircle}
/>
)

expect(wrapper.find(CustomCircle).length).toBe(11)
})
})

it(`should allow to filter labels using a custom filter function`, () => {
const wrapper = mount(
<testCase.Chart<SampleDatum>
width={600}
height={600}
data={sampleData}
enableLabels={true}
labelsFilter={label => label.node.depth === 1}
/>
)

expect(wrapper.find(testCase.label).length).toBe(3)
describe('labels', () => {
it(`should render as much labels as nodes if 'enableLabels' is 'true'`, () => {
const wrapper = mount(
<testCase.Chart<SampleDatum>
width={600}
height={600}
data={sampleData}
enableLabels={true}
/>
)

expect(wrapper.find(testCase.label).length).toBe(11)
})

it(`should render no label if 'enableLabels' is 'false'`, () => {
const wrapper = mount(
<testCase.Chart<SampleDatum>
width={600}
height={600}
data={sampleData}
enableLabels={false}
/>
)

expect(wrapper.find(testCase.label).length).toBe(0)
})

it(`should allow to skip labels using 'labelsSkipRadius' if radius is lower than given value`, () => {
const wrapper = mount(
<testCase.Chart<SampleDatum>
width={600}
height={600}
data={sampleData}
enableLabels={true}
labelsSkipRadius={24}
/>
)

expect(wrapper.find(testCase.label).length).toBe(10)
})

it(`should allow to filter labels using a custom filter function`, () => {
const wrapper = mount(
<testCase.Chart<SampleDatum>
width={600}
height={600}
data={sampleData}
enableLabels={true}
labelsFilter={label => label.node.depth === 1}
/>
)

expect(wrapper.find(testCase.label).length).toBe(3)
})

it('should support a custom label component', () => {
const CustomLabel = () => <span />
const wrapper = mount(
<testCase.Chart<SampleDatum>
width={600}
height={600}
data={sampleData}
enableLabels={true}
labelsComponent={CustomLabel}
/>
)

expect(wrapper.find(CustomLabel).length).toBe(11)
})
})
})
})
Expand Down
3 changes: 2 additions & 1 deletion website/src/data/components/circle-packing/mapper.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { settingsMapper } from '../../../lib/settings'
import { mapFormat, settingsMapper } from '../../../lib/settings'

export default settingsMapper({
valueFormat: mapFormat,
label: value => {
if (value === `d => \`\${d.id}: \${d.value}\``) return d => `${d.id}: ${d.value}`
return value
Expand Down
37 changes: 33 additions & 4 deletions website/src/data/components/circle-packing/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ const props = [
required: false,
defaultValue: defaultProps.value,
},
{
key: 'valueFormat',
group: 'Base',
help: 'Optional formatter for values.',
description: `
The formatted value can then be used for labels & tooltips.
Under the hood, nivo uses [d3-format](https://github.com/d3/d3-format),
please have a look at it for available formats, you can also pass a function
which will receive the raw value and should return the formatted one.
`,
required: false,
type: 'string | (value: number) => string | number',
controlType: 'valueFormat',
},
{
key: 'padding',
help: 'Padding between each circle.',
Expand Down Expand Up @@ -205,6 +220,12 @@ const props = [
group: 'Style',
},
...defsProperties('Style', ['svg']),
{
key: 'circleComponent',
help: 'Custom circle component.',
group: 'Style',
required: false,
},
{
key: 'enableLabels',
help: 'Enable/disable labels.',
Expand All @@ -228,10 +249,12 @@ const props = [
controlType: 'choices',
group: 'Labels',
controlOptions: {
choices: ['id', 'value', `d => \`\${d.id}: \${d.value}\``].map(choice => ({
label: choice,
value: choice,
})),
choices: ['id', 'value', 'formattedValue', `d => \`\${d.id}: \${d.value}\``].map(
choice => ({
label: choice,
value: choice,
})
),
},
},
{
Expand Down Expand Up @@ -261,6 +284,12 @@ const props = [
controlType: 'inheritedColor',
group: 'Labels',
},
{
key: 'labelsComponent',
help: 'Custom label component.',
group: 'Labels',
required: false,
},
{
key: 'isInteractive',
flavors: ['svg', 'html', 'canvas'],
Expand Down
1 change: 1 addition & 0 deletions website/src/pages/circle-packing/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const CirclePackingApi = () => {
root: JSON.stringify(root, null, ' '),
identity: 'name',
value: 'loc',
valueFormat: { format: '', enabled: false },
colors: { scheme: 'nivo' },
colorBy: 'depth',
padding: 1,
Expand Down
Loading

0 comments on commit 6f8a4ca

Please sign in to comment.