Skip to content
Merged
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
5 changes: 1 addition & 4 deletions src/components/Charts/BarChart/BarChartContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Animated, {useAnimatedStyle, useSharedValue} from 'react-native-reanimate
import type {CartesianChartRenderArg, ChartBounds, PointsArray, Scale} from 'victory-native';
import {Bar, CartesianChart} from 'victory-native';
import ActivityIndicator from '@components/ActivityIndicator';
import BAR_INNER_PADDING from '@components/Charts/barChartConstants';
import ChartTooltipLayer from '@components/Charts/components/ChartTooltipLayer';
import ChartXAxisLabels from '@components/Charts/components/ChartXAxisLabels';
import ChartYAxisLabels from '@components/Charts/components/ChartYAxisLabels';
Expand All @@ -29,9 +30,6 @@ import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan
import variables from '@styles/variables';
import type {CartesianChartProps, ChartDataPoint} from '..';

/** Inner padding between bars (0.3 = 30% of bar width) */
const BAR_INNER_PADDING = 0.3;

/** Extra pixel spacing between the chart boundary and the data range, applied per side (Victory's `domainPadding` prop)
* We need bottom: 1 for proper display of the bottom label
*/
Expand Down Expand Up @@ -312,4 +310,3 @@ function BarChartContent(props: BarChartProps) {

export default BarChartContent;
export type {BarChartProps};
export {BAR_INNER_PADDING};
4 changes: 4 additions & 0 deletions src/components/Charts/barChartConstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** Inner padding between bars (0.3 = 30% of bar width) */
const BAR_INNER_PADDING = 0.3;

export default BAR_INNER_PADDING;
11 changes: 11 additions & 0 deletions src/components/Charts/context/ChartFontsLoaderProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import useChartFonts from '@components/Charts/hooks/useChartFonts';
import {ChartFontsContext} from './ChartFontsContext';

function ChartFontsLoaderProvider({children}: {children: React.ReactNode}) {
const fonts = useChartFonts();

return <ChartFontsContext.Provider value={fonts}>{children}</ChartFontsContext.Provider>;
}

export default ChartFontsLoaderProvider;
8 changes: 1 addition & 7 deletions src/components/Charts/context/ChartFontsProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
import React from 'react';
import useChartFonts from '@components/Charts/hooks/useChartFonts';
import type ChartFontsValue from '@components/Charts/types/chartFontsTypes';
import {ChartFontsContext} from './ChartFontsContext';
import ChartFontsLoaderProvider from './ChartFontsLoaderProvider';

type ChartFontsProviderProps = {
children: React.ReactNode;
value?: ChartFontsValue;
};

function ChartFontsLoaderProvider({children}: {children: React.ReactNode}) {
const fonts = useChartFonts();

return <ChartFontsContext.Provider value={fonts}>{children}</ChartFontsContext.Provider>;
}

function ChartFontsProvider({children, value}: ChartFontsProviderProps) {
if (value) {
return <ChartFontsContext.Provider value={value}>{children}</ChartFontsContext.Provider>;
Expand Down
1 change: 0 additions & 1 deletion src/components/Charts/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export {default as useChartParagraphs} from './useChartParagraphs';
export {default as useYAxisLabelWidth} from './useYAxisLabelWidth';
export {useChartFontManager} from '@components/Charts/context/ChartFontsContext';
export {default as ChartFontsProvider} from '@components/Charts/context/ChartFontsProvider';
export {useChartTypefaces} from '@components/Charts/context/ChartFontsContext';
export {useChartInteractions, TOOLTIP_BAR_GAP} from './useChartInteractions';
export type {HitTestArgs} from './useChartInteractions';
export {default as useChartLabelFormats} from './useChartLabelFormats';
Expand Down
22 changes: 22 additions & 0 deletions src/components/Charts/utils/buildSkiaFontManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type {SkTypefaceFontProvider} from '@shopify/react-native-skia';
import {Skia} from '@shopify/react-native-skia';
import type {ChartDefaultTypeface} from '@components/Charts/types/chartSkiaTypefaceTypes';
import {CHART_FONT_MGR_FROM_TYPEFACES} from './chartFontConstants';

function buildSkiaFontManager(typefaces: ChartDefaultTypeface): SkTypefaceFontProvider {
const fontMgr = Skia.TypefaceFontProvider.Make();

for (const [familyName, typefaceKeys] of Object.entries(CHART_FONT_MGR_FROM_TYPEFACES)) {
for (const typefaceKey of typefaceKeys) {
const typeface = typefaces[typefaceKey];

if (typeface) {
fontMgr.registerFont(typeface, familyName);
}
}
}

return fontMgr;
}

export default buildSkiaFontManager;
14 changes: 2 additions & 12 deletions src/components/Charts/utils/chartFontsCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {Image} from 'react-native';
import type ChartFontsValue from '@components/Charts/types/chartFontsTypes';
import type {ChartDefaultTypeface, ChartSkiaTypefaceKey} from '@components/Charts/types/chartSkiaTypefaceTypes';
import Log from '@libs/Log';
import buildSkiaFontManager from './buildSkiaFontManager';
import {CHART_FONT_MGR_SUPPLEMENTAL_ASSETS, CHART_SKIA_TYPEFACE_ASSETS} from './chartFontAssets';
import {CHART_FONT_MGR_FROM_TYPEFACES} from './chartFontConstants';

const EMPTY_CHART_FONTS: ChartFontsValue = {
typefaces: Object.fromEntries((Object.keys(CHART_SKIA_TYPEFACE_ASSETS) as ChartSkiaTypefaceKey[]).map((key) => [key, null])) as ChartDefaultTypeface,
Expand Down Expand Up @@ -54,17 +54,7 @@ function loadChartSkiaTypefaces(): Promise<ChartDefaultTypeface> {
}

function buildChartFontsValue(typefaces: ChartDefaultTypeface): Promise<ChartFontsValue> {
const fontMgr = Skia.TypefaceFontProvider.Make();

for (const [familyName, typefaceKeys] of Object.entries(CHART_FONT_MGR_FROM_TYPEFACES)) {
for (const typefaceKey of typefaceKeys) {
const typeface = typefaces[typefaceKey];

if (typeface) {
fontMgr.registerFont(typeface, familyName);
}
}
}
const fontMgr = buildSkiaFontManager(typefaces);

return Promise.all(
Object.entries(CHART_FONT_MGR_SUPPLEMENTAL_ASSETS).map(async ([familyName, asset]) => {
Expand Down
34 changes: 2 additions & 32 deletions src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import FontUtils from '@styles/utils/FontUtils';
import type ChildrenProps from '@src/types/utils/ChildrenProps';
import {computeEmbeddedMaxWidth, isChildOfTaskTitle} from './htmlEngineUtils';
import htmlRenderers from './HTMLRenderers';
import VICTORY_HTML_ELEMENT_MODELS from './HTMLRenderers/VictoryChartRenderer/victoryHtmlElementModels';

type BaseHTMLEngineProviderProps = ChildrenProps & {
/** Whether text elements should be selectable */
Expand Down Expand Up @@ -200,38 +201,7 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim
tagName: 'sparkles-icon',
contentModel: HTMLContentModel.mixed,
}),
victorychart: HTMLElementModel.fromCustomModel({
tagName: 'victorychart',
contentModel: HTMLContentModel.block,
}),
victorybar: HTMLElementModel.fromCustomModel({
tagName: 'victorybar',
contentModel: HTMLContentModel.block,
}),
victoryline: HTMLElementModel.fromCustomModel({
tagName: 'victoryline',
contentModel: HTMLContentModel.block,
}),
victoryaxis: HTMLElementModel.fromCustomModel({
tagName: 'victoryaxis',
contentModel: HTMLContentModel.block,
}),
victorylabel: HTMLElementModel.fromCustomModel({
tagName: 'victorylabel',
contentModel: HTMLContentModel.textual,
}),
victorylegend: HTMLElementModel.fromCustomModel({
tagName: 'victorylegend',
contentModel: HTMLContentModel.block,
}),
victorygroup: HTMLElementModel.fromCustomModel({
tagName: 'victorygroup',
contentModel: HTMLContentModel.block,
}),
victorypie: HTMLElementModel.fromCustomModel({
tagName: 'victorypie',
contentModel: HTMLContentModel.block,
}),
...VICTORY_HTML_ELEMENT_MODELS,
}),
[
styles.taskTitleMenuItem,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import type {TNode} from 'react-native-render-html';
import {Bar} from 'victory-native';
import {BAR_INNER_PADDING} from '@components/Charts/BarChart/BarChartContent';
import BAR_INNER_PADDING from '@components/Charts/barChartConstants';
import VictoryTheme from '@components/Charts/VictoryTheme';
import {useVictoryChartRenderArgs} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartRenderArgsContext';
import getYKey from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getYKey';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import type {TNode} from 'react-native-render-html';
import {BarGroup} from 'victory-native';
import {BAR_INNER_PADDING} from '@components/Charts/BarChart/BarChartContent';
import BAR_INNER_PADDING from '@components/Charts/barChartConstants';
import VictoryTheme from '@components/Charts/VictoryTheme';
import {useVictoryChartRenderArgs} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartRenderArgsContext';
import getYKey from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getYKey';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import {CartesianChart} from 'victory-native';
import {ChartFontsProvider} from '@components/Charts/hooks';
import ChartFontsLoaderProvider from '@components/Charts/context/ChartFontsLoaderProvider';
import {useVictoryChartContext} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext';
import {VictoryChartRenderArgsProvider} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartRenderArgsContext';
import getHierarchyID from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getHierarchyID';
Expand All @@ -27,7 +27,7 @@ function VictoryChartCartesian() {
padding={padding}
renderOutside={(renderArgs) => (
// Chart font context does not propagate across the Skia renderOutside boundary.
<ChartFontsProvider>
<ChartFontsLoaderProvider>
<VictoryChartRenderArgsProvider value={renderArgs}>
{labelItems.map((labelItem) => (
<VictoryChartLabel
Expand All @@ -42,7 +42,7 @@ function VictoryChartCartesian() {
/>
))}
</VictoryChartRenderArgsProvider>
</ChartFontsProvider>
</ChartFontsLoaderProvider>
)}
>
{(renderArgs) => (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Skia, Text as SkText} from '@shopify/react-native-skia';
import type {Color, SkFont} from '@shopify/react-native-skia';
import React from 'react';
import {useChartTypefaces} from '@components/Charts/hooks';
import {useChartTypefaces} from '@components/Charts/context/ChartFontsContext';
import getChartSkiaTypeface from '@components/Charts/utils/getChartSkiaTypeface';
import type {LabelItem} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
import computeTextAnchorPosition from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/computeTextAnchorPosition';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Circle, Skia, Text as SkText} from '@shopify/react-native-skia';
import type {Color, SkFont} from '@shopify/react-native-skia';
import React, {Fragment} from 'react';
import {useChartTypefaces} from '@components/Charts/hooks';
import {useChartTypefaces} from '@components/Charts/context/ChartFontsContext';
import getChartSkiaTypeface from '@components/Charts/utils/getChartSkiaTypeface';
import type {LegendItem} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import {PolarChart} from 'victory-native';
import {ChartFontsProvider} from '@components/Charts/hooks';
import ChartFontsLoaderProvider from '@components/Charts/context/ChartFontsLoaderProvider';
import {COLOR_KEY, LABEL_KEY, VALUE_KEY} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/constants';
import {useVictoryChartContext} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/context/VictoryChartContext';
import getHierarchyID from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/getHierarchyID';
Expand All @@ -22,7 +22,7 @@ function VictoryChartPolar() {
colorKey={COLOR_KEY}
>
{/* Chart font context does not propagate into polar Skia children. */}
<ChartFontsProvider>
<ChartFontsLoaderProvider>
{tnode.children.map((child) => (
<VictoryChartCategories
key={`${child.tagName ?? 'node'}-${getHierarchyID(child)}`}
Expand All @@ -41,7 +41,7 @@ function VictoryChartPolar() {
{...legendItem}
/>
))}
</ChartFontsProvider>
</ChartFontsLoaderProvider>
</PolarChart>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, {createContext, useContext} from 'react';
import type {TNode} from 'react-native-render-html';
import {useChartTypefaces} from '@components/Charts/hooks';
import {CHART_TYPE, LABEL_KEY, X_KEY} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/constants';
import {useChartTypefaces} from '@components/Charts/context/ChartFontsContext';
import processVictoryChartTree from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/parsers/processVictoryChartTree';
import type {ChartType, ProcessNodeResult} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
import parseStyles from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/parseStyles';
import resolveVictoryChartType from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/resolveVictoryChartType';
import Log from '@libs/Log';

type VictoryChartContextValue = {
Expand Down Expand Up @@ -46,19 +46,7 @@ function VictoryChartProvider({tnode, children}: {tnode: TNode; children: React.

const {data, xKey, yKeys, xAxis, yAxis, domain, domainPadding, padding, isHorizontal, categories, labelItems, legendItems} = processedResult;
const {nodeStyles: chartContentStyles, parentNodeStyles: chartContainerStyles} = parseStyles(tnode);

const hasCartesianData = Object.values(data).some((entry) => X_KEY in entry);
const hasPolarData = Object.values(data).some((entry) => LABEL_KEY in entry);
let type: ChartType | null = null;

// XNOR Check. There must be one and only one valid chart
if (hasCartesianData === hasPolarData) {
type = null;
} else if (hasCartesianData) {
type = CHART_TYPE.CARTESIAN;
} else if (hasPolarData) {
type = CHART_TYPE.POLAR;
}
const type = resolveVictoryChartType(data);

if (!type) {
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {CHART_TYPE, LABEL_KEY, X_KEY} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/constants';
import type {ChartType, ProcessNodeResult} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';

function resolveVictoryChartType(data: ProcessNodeResult['data']): ChartType | null {
const hasCartesianData = Object.values(data).some((entry) => X_KEY in entry);
const hasPolarData = Object.values(data).some((entry) => LABEL_KEY in entry);

if (hasCartesianData === hasPolarData) {
return null;
}

if (hasCartesianData) {
return CHART_TYPE.CARTESIAN;
}

return CHART_TYPE.POLAR;
}

export default resolveVictoryChartType;
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {HTMLContentModel, HTMLElementModel} from 'react-native-render-html';

const VICTORY_HTML_ELEMENT_MODELS = {
victorychart: HTMLElementModel.fromCustomModel({
tagName: 'victorychart',
contentModel: HTMLContentModel.block,
}),
victorybar: HTMLElementModel.fromCustomModel({
tagName: 'victorybar',
contentModel: HTMLContentModel.block,
}),
victoryline: HTMLElementModel.fromCustomModel({
tagName: 'victoryline',
contentModel: HTMLContentModel.block,
}),
victoryaxis: HTMLElementModel.fromCustomModel({
tagName: 'victoryaxis',
contentModel: HTMLContentModel.block,
}),
victorylabel: HTMLElementModel.fromCustomModel({
tagName: 'victorylabel',
contentModel: HTMLContentModel.textual,
}),
victorylegend: HTMLElementModel.fromCustomModel({
tagName: 'victorylegend',
contentModel: HTMLContentModel.block,
}),
victorygroup: HTMLElementModel.fromCustomModel({
tagName: 'victorygroup',
contentModel: HTMLContentModel.block,
}),
victorypie: HTMLElementModel.fromCustomModel({
tagName: 'victorypie',
contentModel: HTMLContentModel.block,
}),
} as const;

export default VICTORY_HTML_ELEMENT_MODELS;
46 changes: 46 additions & 0 deletions tests/unit/resolveVictoryChartTypeTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {CHART_TYPE, LABEL_KEY, X_KEY} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/constants';
import type {ProcessNodeResult} from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/types';
import resolveVictoryChartType from '@components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer/utils/resolveVictoryChartType';

describe('resolveVictoryChartType', () => {
it('returns null when data has both cartesian and polar keys', () => {
const data: ProcessNodeResult['data'] = {
mixed: {
[X_KEY]: 'Jan',
[LABEL_KEY]: 'Category',
y0: 100,
value: 100,
color: '#000',
},
};

expect(resolveVictoryChartType(data)).toBeNull();
});

it('returns null when data has neither cartesian nor polar keys', () => {
expect(resolveVictoryChartType({})).toBeNull();
});

it('returns cartesian when data entries include x keys only', () => {
const data: ProcessNodeResult['data'] = {
series: {
[X_KEY]: 'Jan',
y0: 100,
},
};

expect(resolveVictoryChartType(data)).toBe(CHART_TYPE.CARTESIAN);
});

it('returns polar when data entries include label keys only', () => {
const data: ProcessNodeResult['data'] = {
slice: {
[LABEL_KEY]: 'Travel',
value: 100,
color: '#000',
},
};

expect(resolveVictoryChartType(data)).toBe(CHART_TYPE.POLAR);
});
});
Loading