Skip to content

Commit 08a3a16

Browse files
committed
rotate axis labels only when necessary
1 parent 4ceb1a5 commit 08a3a16

File tree

2 files changed

+114
-31
lines changed

2 files changed

+114
-31
lines changed

apps/web/src/components/v2Editor/customBlocks/visualizationV2/VisualizationView.tsx

+113-23
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ interface Props {
4545
onNewSQL: () => void
4646
controlsHidden: boolean
4747
isFullScreen: boolean
48-
renderer?: 'canvas' | 'svg'
4948
isHidden: boolean
5049
onToggleHidden: () => void
5150
onExportToPNG?: () => void
@@ -70,7 +69,6 @@ function VisualizationViewV2(props: Props) {
7069
<BrieferResult
7170
title={props.title}
7271
result={props.result}
73-
renderer={props.renderer}
7472
input={props.input}
7573
hasControls={props.hasControls}
7674
/>
@@ -182,11 +180,10 @@ function BrieferResult(props: {
182180
input: VisualizationV2BlockInput
183181
hasControls: boolean
184182
result: VisualizationV2BlockOutputResult
185-
renderer?: 'canvas' | 'svg'
186183
}) {
187184
const [size, setSize] = useResettableState(
188185
() => null as { width: number; height: number } | null,
189-
[props.result, props.renderer]
186+
[props.result]
190187
)
191188

192189
const measureDiv = useRef<HTMLDivElement>(null)
@@ -256,6 +253,7 @@ function BrieferResult(props: {
256253

257254
return {
258255
...props.result,
256+
backgroundColor: '#fff',
259257
legend: {
260258
...props.result.legend,
261259
padding: props.hasControls
@@ -274,7 +272,8 @@ function BrieferResult(props: {
274272
xAxis: props.result.xAxis.map((axis) => ({
275273
...axis,
276274
axisLabel: {
277-
hideOverlap: true,
275+
hideOverlap: axis.type !== 'category',
276+
interval: axis.type === 'category' ? 0 : 'auto',
278277
},
279278
splitLine: {
280279
show: false,
@@ -315,12 +314,7 @@ function BrieferResult(props: {
315314

316315
return (
317316
<div ref={container} className="ph-no-capture h-full">
318-
<Echarts
319-
width={size.width}
320-
height={size.height}
321-
option={option}
322-
renderer={props.renderer}
323-
/>
317+
<Echarts width={size.width} height={size.height} option={option} />
324318
</div>
325319
)
326320
}
@@ -329,32 +323,128 @@ interface EchartsProps {
329323
width: number
330324
height: number
331325
option: echarts.EChartsOption
332-
renderer?: 'canvas' | 'svg'
333326
}
334327
function Echarts(props: EchartsProps) {
335328
const ref = useRef<HTMLDivElement>(null)
336-
const [chart, setChart] = useState<echarts.ECharts | null>(null)
329+
const hiddenRef = useRef<HTMLDivElement>(null)
330+
const [finalOption, setFinalOption] = useState(props.option)
331+
const [isReady, setIsReady] = useState(false)
337332

333+
// First render in hidden div to calculate layout
338334
useEffect(() => {
339-
if (!ref.current) {
340-
return
335+
if (!hiddenRef.current) return
336+
337+
const hiddenChart = echarts.init(hiddenRef.current, null, {
338+
renderer: 'svg',
339+
})
340+
hiddenChart.setOption({
341+
...props.option,
342+
// set animation to be as fast as possible, since finished event does not get fired when no animation
343+
animationDelay: 0,
344+
animationDuration: 1,
345+
})
346+
347+
const handleFinished = () => {
348+
const xAxes = Array.isArray(props.option.xAxis)
349+
? props.option.xAxis
350+
: props.option.xAxis
351+
? [props.option.xAxis]
352+
: []
353+
let isRotated = false
354+
const nextXAxes = xAxes.map((xAxis) => {
355+
if (!xAxis || xAxis.type !== 'category') {
356+
return xAxis
357+
}
358+
359+
const labels = hiddenChart.getZr().dom?.querySelectorAll('text') ?? []
360+
let hasOverlap = false
361+
362+
for (let i = 0; i < labels.length - 1; i++) {
363+
const rect1 = labels[i].getBoundingClientRect()
364+
const rect2 = labels[i + 1].getBoundingClientRect()
365+
if (
366+
rect1.right > rect2.left &&
367+
rect1.left < rect2.right &&
368+
rect1.bottom > rect2.top &&
369+
rect1.top < rect2.bottom
370+
) {
371+
hasOverlap = true
372+
break
373+
}
374+
}
375+
376+
if (hasOverlap) {
377+
isRotated = true
378+
return {
379+
...xAxis,
380+
axisLabel: {
381+
...xAxis.axisLabel,
382+
rotate: 45,
383+
},
384+
}
385+
}
386+
return xAxis
387+
})
388+
389+
setFinalOption({
390+
...props.option,
391+
xAxis: nextXAxes,
392+
// if isRotated we need additional padding left
393+
grid: props.option.grid
394+
? Array.isArray(props.option.grid)
395+
? props.option.grid.map((grid) => ({
396+
...grid,
397+
left: isRotated ? '60' : grid.left,
398+
}))
399+
: {
400+
...props.option.grid,
401+
left: isRotated ? '60' : props.option.grid.left,
402+
}
403+
: isRotated
404+
? {
405+
left: '60',
406+
}
407+
: undefined,
408+
})
409+
setIsReady(true)
410+
hiddenChart.dispose()
341411
}
342412

343-
const chart = echarts.init(ref.current, { renderer: props.renderer })
344-
setChart(chart)
413+
hiddenChart.on('finished', handleFinished)
345414

346415
return () => {
347-
chart.dispose()
416+
hiddenChart.dispose()
348417
}
349-
}, [ref.current, props.renderer])
418+
}, [props.option])
350419

420+
// Only render the visible chart once we have the final layout
351421
useEffect(() => {
352-
if (chart) {
353-
chart.setOption(props.option)
422+
if (!ref.current || !isReady) {
423+
return
354424
}
355-
}, [props.option, chart])
356425

357-
return <div ref={ref} className="w-full h-full" />
426+
const chart = echarts.init(ref.current, null, { renderer: 'canvas' })
427+
chart.setOption(finalOption)
428+
429+
return () => {
430+
chart.dispose()
431+
}
432+
}, [ref.current, isReady, finalOption])
433+
434+
return (
435+
<>
436+
<div
437+
ref={hiddenRef}
438+
className="w-full h-full"
439+
style={{
440+
position: 'absolute',
441+
visibility: 'hidden',
442+
pointerEvents: 'none',
443+
}}
444+
/>
445+
{isReady && <div ref={ref} className="w-full h-full" />}
446+
</>
447+
)
358448
}
359449

360450
function BigNumberVisualization(props: {

apps/web/src/components/v2Editor/customBlocks/visualizationV2/index.tsx

+1-8
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ interface Props {
108108
position: 'before' | 'after'
109109
) => void
110110
dashboardMode: DashboardMode | null
111-
renderer?: 'canvas' | 'svg'
112111
hasMultipleTabs: boolean
113112
isBlockHiddenInPublished: boolean
114113
onToggleIsBlockHiddenInPublished: (blockId: string) => void
@@ -334,11 +333,7 @@ function VisualizationBlockV2(props: Props) {
334333

335334
const onExportToPNG = async () => {
336335
// we don't need to check if props.renderer is undefined because the application sets as 'canvas' in this case
337-
if (
338-
props.renderer === 'svg' ||
339-
attrs.input.chartType === 'number' ||
340-
attrs.input.chartType === 'trend'
341-
)
336+
if (attrs.input.chartType === 'number' || attrs.input.chartType === 'trend')
342337
return
343338

344339
// if the controls are visible the canvas shrinks, making the export smaller
@@ -685,7 +680,6 @@ function VisualizationBlockV2(props: Props) {
685680
result={attrs.output?.result ?? null}
686681
controlsHidden={attrs.controlsHidden}
687682
isFullScreen={isFullScreen}
688-
renderer={props.renderer}
689683
isHidden={attrs.controlsHidden}
690684
onToggleHidden={onToggleHidden}
691685
onExportToPNG={onExportToPNG}
@@ -829,7 +823,6 @@ function VisualizationBlockV2(props: Props) {
829823
result={attrs.output?.result ?? null}
830824
controlsHidden={attrs.controlsHidden}
831825
isFullScreen={isFullScreen}
832-
renderer={props.renderer}
833826
isHidden={attrs.controlsHidden}
834827
onToggleHidden={onToggleHidden}
835828
onExportToPNG={onExportToPNG}

0 commit comments

Comments
 (0)