Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ node_modules
.cache
dist
coverage

# Editor directories and files
.vscode/*
!.vscode/extensions.json
2 changes: 2 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import { extent, range, scaleOrdinal } from 'd3'
import { makeLayout } from 'yogurt-layout'
import { LegacyExamples } from './Legacy'
import { BarchartToTestDisableAnimation } from './BarchartToTestDisableAnimation'

const createLinechartDataset = () => {
const maxX = Math.random() * 100
Expand Down Expand Up @@ -390,6 +391,7 @@ const App = () => {
<Linechart />
<StackedBarchart />
<LegacyExamples />
<BarchartToTestDisableAnimation />
</div>
)
}
Expand Down
174 changes: 174 additions & 0 deletions example/src/BarchartToTestDisableAnimation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { useState } from 'react'
import * as _ from 'lodash-es'
import { makeLayout } from 'yogurt-layout'
import {
Bars,
Cartesian,
CartesianConsumer,
Chart,
Elements,
Grid,
ScaleCategorical,
ScaleContinuous,
Svg,
} from '../../src'

type Datum = {
label: string
icon: string
color: string
value: number
}

const iconWidth = 30
const iconHeight = 30

export function BarchartToTestDisableAnimation({}) {
const [dataset, setDataset] = useState<Datum[]>(createDataset)

const layout = makeLayout({
id: 'svg',
width: 600,
height: 600,
padding: 10,
children: [
{
id: 'wrapper',
padding: 20,
children: [{ id: 'chart' }],
},
],
})

return (
<div
style={{
minHeight: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
}}
>
<div>
<button onClick={() => setDataset(createDataset())}>shuffle</button>
</div>

<Svg width={layout.svg.width} height={layout.svg.height}>
<Chart {...layout.chart}>
<Cartesian
x={{ scale: 'linear', domain: [0, 100] }}
y={{
scale: 'band',
domain: dataset.map((d) => d.label),
paddingInner: 0.1,
}}
nice={true}
>
<Grid>
<Grid.YLines stroke="grey" />
<Grid.XLines stroke="grey" />
<Grid.YLabels padding={5} />
<Grid.XLabels padding={5} />
</Grid>

<CartesianConsumer>
{({ xScale: xScaleTmp, yScale: yScaleTmp }) => {
const xScale = xScaleTmp as ScaleContinuous
const yScale = yScaleTmp as ScaleCategorical

return (
<g>
<Bars
data={dataset}
dataKey={(_, i) => i}
enter={{ opacity: 0, fill: 'tomato' }}
x-data={{ base: 0, to: (d) => d.value }}
y-data={(d) => d.label}
fill={(d) => d.color}
disableAnimationByAttr={{ fill: true }}
/>
<Elements
data={dataset}
dataKey={(_, i) => i}
tag="image"
enter={{
// @ts-ignore
href: (d: Datum) => getIconHref(d.icon),
opacity: 0,
width: 0,
height: 0,
}}
x={(d: Datum) => xScale(d.value) - iconWidth - 10}
y={(d: Datum) => yScale(d.label) + yScale.bandwidth() / 2 - iconHeight / 2}
href={(d: Datum) => getIconHref(d.icon)}
opacity={1}
width={iconWidth}
height={iconHeight}
disableAnimation={false}
disableAnimationByAttr={{ href: true }}
/>
</g>
)
}}
</CartesianConsumer>

<Grid>
<Grid.YAxes stroke="white" strokeWidth={2} />
<Grid.XAxes stroke="white" strokeWidth={2} />
</Grid>
</Cartesian>
</Chart>
</Svg>
</div>
)
}

function createDataset() {
const colors = ['#f6cf65', '#FF9C97', '#6b93bd', '#95CFB7', '#D4838F', '#C9B180']
const iconsAndLabels = [
{
label: 'Pictogram_3x3-Basketball_Filled_Black_RGB',
icon: 'https://og-icons.ocsddna.net/icons/countries/large/BRA.png',
},
{
label: 'Pictogram_Gymnastics-Artistic_Outlined_White_RGB',
icon: 'https://og-icons.ocsddna.net/icons/countries/large/ITA.png',
},
{
label: 'Afghanistan',
icon: 'https://og-icons.ocsddna.net/icons/disciplines/VVO_filled_black.png',
},
{
label: 'Argentina',
icon: 'https://og-icons.ocsddna.net/icons/disciplines/TEN_filled_black.png',
},
{
label: 'Australia',
icon: 'https://og-icons.ocsddna.net/icons/disciplines/VVO_filled_black.png',
},
{
label: 'Italy',
icon: 'https://og-icons.ocsddna.net/icons/disciplines/VVO_filled_black.png',
},
]

const casuallySortedColors = _.shuffle(colors)
const casuallySortedIconsAndLabels = _.shuffle(iconsAndLabels)

return colors.map((_colorHex, i) => {
const iconAndLabel = casuallySortedIconsAndLabels[i]
const color = casuallySortedColors[i]

return {
label: i.toString(),
icon: iconAndLabel.icon,
color,
value: _.random(0, 100),
} as Datum
})
}

function getIconHref(icon: string) {
return icon
}
92 changes: 55 additions & 37 deletions src/components/AnimatedDataset.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export function AnimatedDataset({
delay = 0,
tweens: unparsedTweens = {},
disableAnimation = false,
disableAnimationByAttr: unparseDisableAnimationByAttr = {},
easing = DEFAULT_EASE,
}) {
const ref = React.createRef()
Expand All @@ -67,6 +68,10 @@ export function AnimatedDataset({
const init = mapKeys(unparsedInit, parseAttributeName)
const events = mapKeys(unparsedEvents, parseEventName)
const tweens = mapKeys(unparsedTweens, parseAttributeName)
const disableAnimationByAttr = mapKeys(
unparseDisableAnimationByAttr,
parseAttributeName
)

const attrsList = Object.keys(attrs).filter((a) => a !== 'text')
const tweensList = Object.keys(tweens).filter((a) => a !== 'text')
Expand All @@ -79,8 +84,8 @@ export function AnimatedDataset({
.selectAll(tag)
.data(dataset, keyFn)
.join(
(enter) =>
enter
(enter) => {
return enter
.append(tag)
.text(attrs.text)
.call((sel) => {
Expand All @@ -94,69 +99,82 @@ export function AnimatedDataset({
})
})
.call((sel) => {
const tran = disableAnimation
? sel
: sel
.transition()
.ease(easing)
.delay(delay)
.duration(duration)

attrsList.forEach((a) => {
const shouldDisableAnimation =
disableAnimationByAttr.hasOwnProperty(a)
? disableAnimationByAttr[a]
: disableAnimation

const tran = shouldDisableAnimation
? sel
: sel
.transition(a)
.ease(easing)
.delay(delay)
.duration(duration)
.textTween(tweens.text)
tran.attr(a, attrs[a])
})

tweensList.forEach((a) => {
tran.attrTween(a, tweens[a])
})

tran.textTween(tweens.text)
}),
(update) =>
update
})
},
(update) => {
return update
.text(attrs.text)
.call((sel) => {
eventsList.forEach((event) => {
sel.on(event, events[event])
})
})
.call((sel) => {
const tran = disableAnimation
? sel
: sel
.transition()
.ease(easing)
.delay(delay)
.duration(duration)

attrsList.forEach((a) => {
const shouldDisableAnimation =
disableAnimationByAttr.hasOwnProperty(a)
? disableAnimationByAttr[a]
: disableAnimation
const tran = shouldDisableAnimation
? sel
: sel
.transition(a)
.ease(easing)
.delay(delay)
.duration(duration)
.textTween(tweens.text)
tran.attr(a, attrs[a])
})

tweensList.forEach((a) => {
tran.attrTween(a, tweens[a])
})

tran.textTween(tweens.text)
}),
(exit) =>
exit.call((sel) => {
const tran = disableAnimation
? sel
: sel.transition().ease(easing).delay(delay).duration(duration)

})
},
(exit) => {
return exit.call((sel) => {
oldAttrsList.forEach((a) => {
tran.attr(a, fallback(init[a], oldAttrs[a]))
const shouldDisableAnimation =
disableAnimationByAttr.hasOwnProperty(a)
? disableAnimationByAttr[a]
: disableAnimation
const tran = shouldDisableAnimation
? sel
: sel
.transition(a)
.ease(easing)
.delay(delay)
.duration(duration)
.attr(a, fallback(init[a], oldAttrs[a]))
.textTween(tweens.text)
tran.remove()
})

tweensList.forEach((a) => {
tran.attrTween(a, tweens[a])
})

tran.textTween(tweens.text)

tran.remove()
})
}
)
refOldAttrs.current = attrs
}
Expand Down
6 changes: 6 additions & 0 deletions src/components/Elements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export interface ElementsProps<T>
NativeEventHandlers<T> {
data: T[]
tag: string
disableAnimation?: boolean
disableAnimationByAttr?: Record<string, boolean>
}

export function Elements<T>({
Expand All @@ -41,6 +43,8 @@ export function Elements<T>({
duration,
enter,
easing,
disableAnimation = false,
disableAnimationByAttr = {},
...props
}: ElementsProps<T>) {
const animation = useSanitizedCascadingAnimation({ delay, duration, easing })
Expand All @@ -55,6 +59,8 @@ export function Elements<T>({
attrs={attributes}
events={events}
init={enter}
disableAnimation={disableAnimation}
disableAnimationByAttr={disableAnimationByAttr}
{...(animation as any)}
/>
)
Expand Down
1 change: 1 addition & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export interface AnimationIteratees<T> {
delay?: DefaultedIteratee<T, number>
duration?: DefaultedIteratee<T, number>
easing?: (time: number) => number
disableAnimationByAttr?: Record<string, boolean>
}
export interface AnimationProps<T> extends AnimationIteratees<T> {
dataKey?: KeyAccessor<T>
Expand Down