Skip to content

Commit

Permalink
implement number and trend into visualization v2
Browse files Browse the repository at this point in the history
  • Loading branch information
vieiralucas committed Jan 15, 2025
1 parent 1fd4f3d commit bdfc855
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 103 deletions.
19 changes: 3 additions & 16 deletions apps/api/src/python/visualizations-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ def _briefer_create_visualization(df, options):
elif chartType == "histogram":
raise ValueError("Histogram chart is not supported")
elif chartType == "trend":
raise ValueError("Trend chart is not supported")
return "bar", False, False
elif chartType == "number":
raise ValueError("Number chart is not supported")
return "bar", False, False
def convert_value(column, value):
if pd.api.types.is_numeric_dtype(column):
Expand Down Expand Up @@ -182,7 +182,7 @@ def _briefer_create_visualization(df, options):
serie = {
"type": chart_type,
"datasetIndex": dataset_index,
"z": i
"z": i,
}
if group:
Expand Down Expand Up @@ -306,19 +306,6 @@ export async function createVisualizationV2(
if (parsed.success) {
switch (parsed.data.type) {
case 'log':
console.log(
JSON.stringify(
{
workspaceId,
sessionId,
message: parsed.data.message,
input,
},
null,
2
),
'createVisualizationV2 log'
)
logger().info(
{
workspaceId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,17 @@ import useResettableState from '@/hooks/useResettableState'
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
import debounce from 'lodash.debounce'
import { findMaxFontSize, measureText } from '@/measureText'
import { VisualizationV2BlockOutputResult } from '@briefer/editor'
import {
VisualizationV2BlockInput,
VisualizationV2BlockOutputResult,
} from '@briefer/editor'
import { head, last } from 'ramda'

const FONT_FAMILY = ['Inter', ...twFontFamiliy.sans].join(', ')

interface Props {
title: string
chartType: ChartType
input: VisualizationV2BlockInput
result: VisualizationV2BlockOutputResult | null
tooManyDataPointsHidden: boolean
onHideTooManyDataPointsWarning: () => void
Expand Down Expand Up @@ -68,7 +72,7 @@ function VisualizationViewV2(props: Props) {
title={props.title}
result={props.result}
renderer={props.renderer}
chartType={props.chartType}
input={props.input}
isDashboard={props.isDashboard}
/>
{props.loading && (
Expand Down Expand Up @@ -161,8 +165,8 @@ function VisualizationViewV2(props: Props) {
</button>
)}
{!props.isDashboard &&
props.chartType !== 'number' &&
props.chartType !== 'trend' && (
props.input.chartType !== 'number' &&
props.input.chartType !== 'trend' && (
<button
className="absolute bottom-0 bg-white rounded-tl-md rounded-br-md border-t border-l border-gray-200 p-1 hover:bg-gray-50 z-10 right-0 text-xs text-gray-400"
onClick={props.onExportToPNG}
Expand All @@ -176,7 +180,7 @@ function VisualizationViewV2(props: Props) {

function BrieferResult(props: {
title: string
chartType: ChartType
input: VisualizationV2BlockInput
isDashboard: boolean
result: VisualizationV2BlockOutputResult
renderer?: 'canvas' | 'svg'
Expand Down Expand Up @@ -233,17 +237,17 @@ function BrieferResult(props: {
return <div className="w-full h-full" ref={measureDiv} />
}

if (props.chartType === 'trend' || props.chartType === 'number') {
if (props.input.chartType === 'trend' || props.input.chartType === 'number') {
return (
<div ref={container} className="ph-no-capture h-full">
{/* TODO */}
{/* <BigNumberVisualization */}
{/* chartType={props.chartType} */}
{/* title={props.title} */}
{/* spec={spec} */}
{/* size={size} */}
{/* isDashboard={props.isDashboard} */}
{/* /> */}
<BigNumberVisualization
title={props.title}
chartType={props.input.chartType}
input={props.input}
result={props.result}
size={size}
isDashboard={props.isDashboard}
/>
</div>
)
}
Expand Down Expand Up @@ -298,91 +302,101 @@ function extractXYFromSpec(spec: any) {

function BigNumberVisualization(props: {
title: string
chartType: 'trend' | 'number'
spec: any
input: VisualizationV2BlockInput
chartType: 'number' | 'trend'
result: VisualizationV2BlockOutputResult
size: { width: number; height: number }
isDashboard: boolean
}) {
const { spec, size } = props
const { size } = props

try {
// @ts-ignore
const dataset = spec.datasets[spec.data.name]
// @ts-ignore
const { x, y } = extractXYFromSpec(spec)
const latests = dataset.slice(-2)
const x = props.input.xAxis?.name?.toString()
const y = head(
head(props.input.yAxes)?.series ?? []
)?.column?.name.toString()
if (!x || !y) {
throw new Error('Invalid input')
}

const latests = props.result.dataset[0]?.source.slice(-2)
const latest = latests.pop()
const prev = latests.pop()

if (!latest) {
throw new Error('Invalid result')
}

const lastValue = {
x: latest[x.field],
displayX: latest[x.field],
y: latest[y.field],
displayY: latest[y.field],
x: latest[x],
displayX: latest[x].toString(),
y: Number(latest[y]),
displayY: latest[y].toString(),
}
const prevValue = {
x: prev?.[x.field] ?? 0,
displayX: prev?.[x.field] ?? 0,
y: prev?.[y.field],
displayY: prev?.[y.field] ?? 0,
}
const yFormat = y.axis.format
if (yFormat) {
lastValue.displayY = d3Format(yFormat)(lastValue.y)
prevValue.displayY = d3Format(yFormat)(prevValue.y)
}

const xFormat = x.axis.format
if (xFormat) {
lastValue.displayX = d3Format(xFormat)(lastValue.x)
prevValue.displayX = d3Format(xFormat)(prevValue.x)
} else {
const xType = x.type
switch (xType) {
case 'temporal': {
const timeUnit = x.timeUnit

if (!timeUnit) {
lastValue.displayX = new Date(lastValue.x).toLocaleDateString()
prevValue.displayX = new Date(prevValue.x).toLocaleDateString()
} else {
const timeFormats: Record<string, string> = {
year: '%b %d, %Y %I:00 %p',
yearmonth: '%b %d, %Y %I:00 %p',
yearquarter: '%b %d, %Y %I:00 %p',
yearweek: '%b %d, %Y %I:00 %p',
yearmonthdate: '%b %d, %Y %I:00 %p',
yearmonthdatehours: '%b %d, %Y %I:00 %p',
yearmonthdatehoursminutes: '%b %d, %Y %I:%M %p',
yearmonthdatehoursminutesseconds: '%b %d, %Y %I:%M:%S %p',
}
const format = timeFormats[timeUnit]
if (format) {
const formatter = timeFormat(format)
lastValue.displayX = formatter(new Date(lastValue.x))
prevValue.displayX = formatter(new Date(prevValue.x))
}
}
break
}
case 'quantitative':
lastValue.displayX = d3Format('.2f')(lastValue.x)
prevValue.displayX = d3Format('.2f')(prevValue.x)
break
case 'ordinal':
lastValue.displayX = lastValue.x
prevValue.displayX = prevValue.x
break
}
x: prev?.[x] ?? 0,
displayX: (prev?.[x] ?? 0).toString(),
y: Number(prev?.[y] ?? 0),
displayY: (prev?.[y] ?? 0).toString(),
}
// const yFormat = y.axis.format
// if (yFormat) {
// lastValue.displayY = d3Format(yFormat)(lastValue.y)
// prevValue.displayY = d3Format(yFormat)(prevValue.y)
// }

// TODO: formatting
// const xFormat = x.axis.format
// if (false) { // xFormat) {
// lastValue.displayX = d3Format(xFormat)(lastValue.x)
// prevValue.displayX = d3Format(xFormat)(prevValue.x)
// } else {
// const xType = x.type
// switch (xType) {
// case 'temporal': {
// const timeUnit = x.timeUnit

// if (!timeUnit) {
// lastValue.displayX = new Date(lastValue.x).toLocaleDateString()
// prevValue.displayX = new Date(prevValue.x).toLocaleDateString()
// } else {
// const timeFormats: Record<string, string> = {
// year: '%b %d, %Y %I:00 %p',
// yearmonth: '%b %d, %Y %I:00 %p',
// yearquarter: '%b %d, %Y %I:00 %p',
// yearweek: '%b %d, %Y %I:00 %p',
// yearmonthdate: '%b %d, %Y %I:00 %p',
// yearmonthdatehours: '%b %d, %Y %I:00 %p',
// yearmonthdatehoursminutes: '%b %d, %Y %I:%M %p',
// yearmonthdatehoursminutesseconds: '%b %d, %Y %I:%M:%S %p',
// }
// const format = timeFormats[timeUnit]
// if (format) {
// const formatter = timeFormat(format)
// lastValue.displayX = formatter(new Date(lastValue.x))
// prevValue.displayX = formatter(new Date(prevValue.x))
// }
// }
// break
// }
// case 'quantitative':
// lastValue.displayX = d3Format('.2f')(lastValue.x)
// prevValue.displayX = d3Format('.2f')(prevValue.x)
// break
// case 'ordinal':
// lastValue.displayX = lastValue.x
// prevValue.displayX = prevValue.x
// break
// }
// }

const minDimension = Math.min(size.width, size.height)
const fontSize = Math.min(
Math.max(
8,
Math.min(
findMaxFontSize(
lastValue.displayY,
lastValue.displayY.toString(),
minDimension / 3,
size.width - 32,
'bold',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
getDataframeFromVisualizationV2,
getVisualizationV2Attributes,
isVisualizationV2Block,
VisualizationV2BlockInput,
} from '@briefer/editor'
import { ApiDocument } from '@briefer/database'
import { FunnelIcon } from '@heroicons/react/24/outline'
Expand Down Expand Up @@ -271,10 +272,53 @@ function VisualizationBlockV2(props: Props) {

const onChangeChartType = useCallback(
(chartType: ChartType) => {
props.block.setAttribute('input', {
...attrs.input,
chartType,
})
let nextInput: VisualizationV2BlockInput
switch (chartType) {
case 'trend':
case 'number':
const series = attrs.input.yAxes[0]?.series[0] ?? null
nextInput = {
dataframeName: attrs.input.dataframeName,
chartType,
xAxis: attrs.input.xAxis,
xAxisName: attrs.input.xAxisName,
xAxisSort: attrs.input.xAxisSort,
xAxisGroupFunction: attrs.input.xAxisGroupFunction,
yAxes: series
? [
{
series: [
{
axisName: series.axisName,
chartType: null,
column: series.column,
aggregateFunction: series.aggregateFunction,
groupBy: null,
},
],
},
]
: [],
filters: attrs.input.filters,
}
break
case 'groupedColumn':
case 'line':
case 'area':
case 'scatterPlot':
case 'stackedColumn':
case 'hundredPercentStackedArea':
case 'hundredPercentStackedColumn':
case 'pie':
case 'histogram':
nextInput = {
...attrs.input,
chartType,
}
break
}

props.block.setAttribute('input', nextInput)
},
[props.block, attrs.input]
)
Expand Down Expand Up @@ -455,7 +499,7 @@ function VisualizationBlockV2(props: Props) {
return (
<VisualizationViewV2
title={attrs.title}
chartType={attrs.input.chartType}
input={attrs.input}
tooManyDataPointsHidden={tooManyDataPointsHidden}
onHideTooManyDataPointsWarning={onHideTooManyDataPointsWarning}
loading={viewLoading}
Expand Down Expand Up @@ -591,7 +635,7 @@ function VisualizationBlockV2(props: Props) {
/>
<VisualizationViewV2
title={attrs.title}
chartType={attrs.input.chartType}
input={attrs.input}
tooManyDataPointsHidden={tooManyDataPointsHidden}
onHideTooManyDataPointsWarning={onHideTooManyDataPointsWarning}
loading={viewLoading}
Expand Down

0 comments on commit bdfc855

Please sign in to comment.