Skip to content

Commit

Permalink
fix: balance rotation/tickEstimates/tickSkipping
Browse files Browse the repository at this point in the history
  • Loading branch information
tannerlinsley committed Aug 19, 2020
1 parent 3141edf commit 9309197
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 66 deletions.
10 changes: 5 additions & 5 deletions examples/line/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ export default function App() {
useLagRadar();

const { data, randomizeData } = useDemoConfig({
series: 10
series: 10,
});

const series = React.useMemo(
() => ({
showPoints: false
showPoints: false,
}),
[]
);
Expand All @@ -29,10 +29,10 @@ export default function App() {
primary: true,
type: "time",
position: "bottom",
filterTicks: ticks =>
ticks.filter(date => +timeDay.floor(date) === +date)
filterTicks: (ticks) =>
ticks.filter((date) => +timeDay.floor(date) === +date),
},
{ type: "linear", position: "left" }
{ type: "linear", position: "left" },
],
[]
);
Expand Down
11 changes: 11 additions & 0 deletions src/components/AxisLinear.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from '../utils/Constants.js'
import useChartContext from '../hooks/useChartContext'
import useMeasure from './AxisLinear.useMeasure'
import useChartState from '../hooks/useChartState'

const defaultStyles = {
line: {
Expand Down Expand Up @@ -49,6 +50,7 @@ export default function AxisLinear(axis) {
tickOffset,
gridOffset,
spacing,
id,
} = axis
const [rotation, setRotation] = React.useState(0)
const { gridWidth, gridHeight, dark } = useChartContext()
Expand All @@ -57,6 +59,10 @@ export default function AxisLinear(axis) {

useMeasure({ ...axis, elRef, rotation, gridWidth, gridHeight, setRotation })

const [tickLabelSkipRatio] = useChartState(
state => state.tickLabelSkipRatios[id]
)

// Not ready? Render null
if (!show) {
return null
Expand Down Expand Up @@ -184,6 +190,11 @@ export default function AxisLinear(axis) {
vertical ? directionMultiplier * spacing : tickOffset,
vertical ? tickOffset : directionMultiplier * spacing
)} rotate(${-rotation}deg)`,
opacity:
!tickLabelSkipRatio ||
(tickLabelSkipRatio > 0 && i % tickLabelSkipRatio === 0)
? 1
: 0,
}}
dominantBaseline={
rotation
Expand Down
130 changes: 79 additions & 51 deletions src/components/AxisLinear.useMeasure.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import React from 'react'
import useChartState from '../hooks/useChartState'
import useIsomorphicLayoutEffect from '../hooks/useIsomorphicLayoutEffect'
import usePrevious from '../hooks/usePrevious'
import { axisTypeOrdinal } from '../utils/Constants'
import { round } from '../utils/Utils'

const radiansToDegrees = r => r * (180 / Math.PI)
const degreesToRadians = d => d * (Math.PI / 180)

const getElBox = el => {
var rect = el.getBoundingClientRect()
return {
Expand All @@ -30,8 +27,6 @@ export default function useMeasure({
position,
tickSizeInner,
tickSizeOuter,
transform,
barSize,
labelRotation,
tickPadding,
tickCount,
Expand All @@ -40,19 +35,20 @@ export default function useMeasure({
vertical,
gridWidth,
gridHeight,
ticks,
scale,
}) {
const disallowListRef = React.useRef([])
const axisDimensionsHistoryRef = React.useRef([])

const [axisDimensions, setChartState] = useChartState(
state => state.axisDimensions
const [estimatedTickCount, setChartState] = useChartState(
state => state.estimatedTickCounts[id]
)
const [tickLabelSkipRatio] = useChartState(
state => state.tickLabelSkipRatios[id]
)
const [axisDimension] = useChartState(
state => state.axisDimensions?.[position]?.[id]
)

const measureDimensions = React.useCallback(() => {
if (!elRef.current) {
if (axisDimensions[position]?.[id]) {
if (axisDimension) {
// If the entire axis is hidden, then we need to remove the axis dimensions
setChartState(state => {
const newAxes = state.axisDimensions[position] || {}
Expand All @@ -70,30 +66,13 @@ export default function useMeasure({
}

let gridSize = !vertical ? gridWidth : gridHeight
let calculatedTickCount = tickCount
let width = 0
let height = 0
let top = 0
let bottom = 0
let left = 0
let right = 0

// Measure the distances between ticks
let smallestTickGap = gridSize // This is just a ridiculously large tick spacing that would never happen (hopefully)

Array(...elRef.current.querySelectorAll('.tick'))
.map(el => getElBox(el))
.reduce((prev, current) => {
if (prev) {
smallestTickGap = Math.min(
smallestTickGap,
vertical ? current.top - prev.top : current.left - prev.left
)
}

return current
}, false)

const domainDims = getElBox(elRef.current.querySelector('.domain'))

const measureLabelDims = Array(
Expand Down Expand Up @@ -134,26 +113,70 @@ export default function useMeasure({
[]
)

// Auto-fit ticks in "auto" tick mode
if (tickCount === 'auto' && type !== 'ordinal') {
const largestMeasureLabelSize = !vertical
? widestMeasureLabel?.width || 0
: tallestMeasureLabel?.height || 0
let smallestTickGap = gridSize

if (measureLabelDims.length > 1) {
measureLabelDims.reduce((prev, current) => {
if (prev) {
smallestTickGap = Math.min(
smallestTickGap,
vertical ? current.top - prev.top : current.left - prev.left
)
}

return current
}, false)
}

const largestMeasureLabelSize = !vertical
? widestMeasureLabel?.width || 0
: tallestMeasureLabel?.height || 0

// Auto-fit ticks in "auto" tick mode for non-ordinal scales
if (tickCount === 'auto' && type !== 'ordinal') {
// if it's on, determine how many ticks we could display if they were all flat
// How many ticks can we fit in the available axis space?
const estimatedTickCount = Math.floor(
(gridSize + largestMeasureLabelSize + tickPadding) /
(largestMeasureLabelSize + tickPadding)
let calculatedTickCount = Math.floor(
(gridSize + largestMeasureLabelSize + tickPadding * 2) /
(largestMeasureLabelSize + tickPadding * 2)
)

calculatedTickCount = Math.max(
Math.min(estimatedTickCount, maxTickCount),
Math.min(calculatedTickCount, maxTickCount),
minTickCount
)

if (calculatedTickCount !== estimatedTickCount) {
setChartState(old => ({
...old,
estimatedTickCounts: {
...old.estimatedTickCounts,
[id]: calculatedTickCount,
},
}))
}
}

if (!vertical) {
// Visual Skipping of axis labels if they overlap (rotation not included)
const newTickLabelSkipRatio = !rotation
? Math.max(
1,
Math.ceil((largestMeasureLabelSize + tickPadding) / smallestTickGap)
)
: 1

if (newTickLabelSkipRatio !== tickLabelSkipRatio) {
setChartState(old => ({
...old,
tickLabelSkipRatios: {
...old.tickLabelSkipRatios,
[id]: newTickLabelSkipRatio,
},
}))
}

// Rotate ticks for non-time horizontal axes
if (!vertical && type === axisTypeOrdinal) {
const newRotation =
(widestMeasureLabel?.width || 0) + tickPadding > smallestTickGap
? labelRotation
Expand All @@ -166,12 +189,16 @@ export default function useMeasure({

// Axis overflow measurements
if (!vertical) {
const leftMostLabelDim = realLabelDims.reduce((d, labelDim) =>
labelDim.left < d.left ? labelDim : d
)
const rightMostLabelDim = realLabelDims.reduce((d, labelDim) =>
labelDim.right > d.right ? labelDim : d
)
const leftMostLabelDim = realLabelDims.length
? realLabelDims.reduce((d, labelDim) =>
labelDim.left < d.left ? labelDim : d
)
: null
const rightMostLabelDim = realLabelDims.length
? realLabelDims.reduce((d, labelDim) =>
labelDim.right > d.right ? labelDim : d
)
: null

left = round(
Math.max(0, domainDims.left - leftMostLabelDim?.left),
Expand Down Expand Up @@ -229,14 +256,13 @@ export default function useMeasure({
bottom,
left,
right,
tickCount: calculatedTickCount,
}

// Only update the axisDimensions if something has changed
if (
!axisDimensions?.[position]?.[id] ||
!axisDimension ||
Object.keys(newDimensions).some(key => {
return newDimensions[key] !== axisDimensions[position][id][key]
return newDimensions[key] !== axisDimension[key]
})
) {
setChartState(state => ({
Expand All @@ -251,8 +277,9 @@ export default function useMeasure({
}))
}
}, [
axisDimensions,
axisDimension,
elRef,
estimatedTickCount,
gridHeight,
gridWidth,
id,
Expand All @@ -264,6 +291,7 @@ export default function useMeasure({
setChartState,
setRotation,
tickCount,
tickLabelSkipRatio,
tickPadding,
tickSizeInner,
tickSizeOuter,
Expand Down
9 changes: 7 additions & 2 deletions src/components/Chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ export default function ChartState(options) {
hovered: null,
element: null,
axisDimensions: {},
estimatedTickCounts: {},
tickLabelSkipRatios: {},
offset: {},
pointer: {},
setOffset,
Expand Down Expand Up @@ -136,11 +138,14 @@ export function Chart(options) {
...rest
} = applyDefaults(options)

const [{ hovered, element, axisDimensions, pointer }] = useChartState(
const [
{ hovered, element, axisDimensions, estimatedTickCounts, pointer },
] = useChartState(
d => ({
hovered: d.hovered,
element: d.element,
axisDimensions: d.axisDimensions,
estimatedTickCounts: d.estimatedTickCounts,
pointer: d.pointer,
}),
'shallow'
Expand Down Expand Up @@ -177,7 +182,7 @@ export function Chart(options) {
materializedData,
gridHeight,
gridWidth,
axisDimensions,
estimatedTickCounts,
})

const stackData = useStackData({
Expand Down
6 changes: 3 additions & 3 deletions src/components/pipeline/useAxes.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default ({
materializedData,
gridHeight,
gridWidth,
axisDimensions,
estimatedTickCounts,
}) => {
// Detect axes changes and build axes
let prePrimaryAxes = axes.filter(d => d.primary)
Expand All @@ -25,7 +25,7 @@ export default ({
materializedData,
gridWidth,
gridHeight,
axisDimensions,
estimatedTickCounts,
})
})
},
Expand All @@ -42,7 +42,7 @@ export default ({
materializedData,
gridWidth,
gridHeight,
axisDimensions,
estimatedTickCounts,
})
})
},
Expand Down
7 changes: 2 additions & 5 deletions src/utils/buildAxis.linear.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export default function buildAxisLinear({
materializedData,
gridHeight,
gridWidth,
axisDimensions,
estimatedTickCounts,
}) {
if (!position) {
throw new Error(`Chart axes must have a valid 'position' property`)
Expand All @@ -84,9 +84,6 @@ export default function buildAxisLinear({
let positiveTotalByKey = {}
let domain

const axisDimension =
axisDimensions && axisDimensions[position] && axisDimensions[position][id]

// Loop through each series
for (
let seriesIndex = 0;
Expand Down Expand Up @@ -262,7 +259,7 @@ export default function buildAxisLinear({
let resolvedTickCount = tickCount

if (tickCount === 'auto') {
resolvedTickCount = axisDimension?.tickCount || 10
resolvedTickCount = estimatedTickCounts[id] ?? 10
}

const ticks = filterTicks(
Expand Down

0 comments on commit 9309197

Please sign in to comment.