From 0f587d0d8029c9709879e67e79df35baa2747bea Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Tue, 1 Oct 2019 10:36:37 -0500 Subject: [PATCH] feat(style): point style overrides (#385) Allow overrides for point styles on area and line charts per datum --- src/chart_types/xy_chart/domains/y_domain.ts | 9 +-- .../xy_chart/rendering/rendering.test.ts | 76 +++++++++++++++---- .../xy_chart/rendering/rendering.ts | 48 +++++++++--- .../store/chart_state.interactions.test.ts | 1 - src/chart_types/xy_chart/store/utils.ts | 2 + src/chart_types/xy_chart/utils/specs.ts | 37 ++++++++- .../react_canvas/area_geometries.tsx | 16 +++- .../react_canvas/line_geometries.tsx | 41 ++++++---- stories/bar_chart.tsx | 36 --------- stories/styling.tsx | 72 ++++++++++++++++++ wiki/overview.md | 29 ++++++- 11 files changed, 276 insertions(+), 91 deletions(-) diff --git a/src/chart_types/xy_chart/domains/y_domain.ts b/src/chart_types/xy_chart/domains/y_domain.ts index d35bedad2f..945bfce29a 100644 --- a/src/chart_types/xy_chart/domains/y_domain.ts +++ b/src/chart_types/xy_chart/domains/y_domain.ts @@ -16,14 +16,7 @@ export type YDomain = BaseDomain & { }; export type YBasicSeriesSpec = Pick< BasicSeriesSpec, - | 'id' - | 'seriesType' - | 'yScaleType' - | 'groupId' - | 'stackAccessors' - | 'yScaleToDataExtent' - | 'styleAccessor' - | 'useDefaultGroupDomain' + 'id' | 'seriesType' | 'yScaleType' | 'groupId' | 'stackAccessors' | 'yScaleToDataExtent' | 'useDefaultGroupDomain' > & { stackAsPercentage?: boolean }; interface GroupSpecs { diff --git a/src/chart_types/xy_chart/rendering/rendering.test.ts b/src/chart_types/xy_chart/rendering/rendering.test.ts index f392c0f3f9..f2b640bab4 100644 --- a/src/chart_types/xy_chart/rendering/rendering.test.ts +++ b/src/chart_types/xy_chart/rendering/rendering.test.ts @@ -4,10 +4,11 @@ import { getGeometryStyle, isPointOnGeometry, PointGeometry, - getStyleOverrides, + getBarStyleOverrides, GeometryId, + getPointStyleOverrides, } from './rendering'; -import { BarSeriesStyle, SharedGeometryStyle } from '../../../utils/themes/theme'; +import { BarSeriesStyle, SharedGeometryStyle, PointStyle } from '../../../utils/themes/theme'; import { DataSeriesDatum } from '../utils/series'; import { RecursivePartial, mergePartial } from '../../../utils/commons'; @@ -169,7 +170,7 @@ describe('Rendering utils', () => { expect(noHover).toBe(sharedThemeStyle.highlighted); }); - describe('getStyleOverrides', () => { + describe('getBarStyleOverrides', () => { let mockAccessor: jest.Mock; const sampleSeriesStyle: BarSeriesStyle = { @@ -205,21 +206,21 @@ describe('Rendering utils', () => { mockAccessor = jest.fn(); }); - it('should return input seriesStyle if no styleAccessor is passed', () => { - const styleOverrides = getStyleOverrides(datum, geometryId, sampleSeriesStyle); + it('should return input seriesStyle if no barStyleAccessor is passed', () => { + const styleOverrides = getBarStyleOverrides(datum, geometryId, sampleSeriesStyle); expect(styleOverrides).toBe(sampleSeriesStyle); }); - it('should return input seriesStyle if styleAccessor returns null', () => { + it('should return input seriesStyle if barStyleAccessor returns null', () => { mockAccessor.mockReturnValue(null); - const styleOverrides = getStyleOverrides(datum, geometryId, sampleSeriesStyle, mockAccessor); + const styleOverrides = getBarStyleOverrides(datum, geometryId, sampleSeriesStyle, mockAccessor); expect(styleOverrides).toBe(sampleSeriesStyle); }); - it('should call styleAccessor with datum and geometryId', () => { - getStyleOverrides(datum, geometryId, sampleSeriesStyle, mockAccessor); + it('should call barStyleAccessor with datum and geometryId', () => { + getBarStyleOverrides(datum, geometryId, sampleSeriesStyle, mockAccessor); expect(mockAccessor).toBeCalledWith(datum, geometryId); }); @@ -227,7 +228,7 @@ describe('Rendering utils', () => { it('should return seriesStyle with updated fill color', () => { const color = 'blue'; mockAccessor.mockReturnValue(color); - const styleOverrides = getStyleOverrides(datum, geometryId, sampleSeriesStyle, mockAccessor); + const styleOverrides = getBarStyleOverrides(datum, geometryId, sampleSeriesStyle, mockAccessor); const expectedStyles: BarSeriesStyle = { ...sampleSeriesStyle, rect: { @@ -240,7 +241,7 @@ describe('Rendering utils', () => { it('should return a new seriesStyle object with color', () => { mockAccessor.mockReturnValue('blue'); - const styleOverrides = getStyleOverrides(datum, geometryId, sampleSeriesStyle, mockAccessor); + const styleOverrides = getBarStyleOverrides(datum, geometryId, sampleSeriesStyle, mockAccessor); expect(styleOverrides).not.toBe(sampleSeriesStyle); }); @@ -255,7 +256,7 @@ describe('Rendering utils', () => { }, }; mockAccessor.mockReturnValue(partialStyle); - const styleOverrides = getStyleOverrides(datum, geometryId, sampleSeriesStyle, mockAccessor); + const styleOverrides = getBarStyleOverrides(datum, geometryId, sampleSeriesStyle, mockAccessor); const expectedStyles = mergePartial(sampleSeriesStyle, partialStyle, { mergeOptionalPartialValues: true, }); @@ -269,9 +270,58 @@ describe('Rendering utils', () => { fill: 'blue', }, }); - const styleOverrides = getStyleOverrides(datum, geometryId, sampleSeriesStyle, mockAccessor); + const styleOverrides = getBarStyleOverrides(datum, geometryId, sampleSeriesStyle, mockAccessor); expect(styleOverrides).not.toBe(sampleSeriesStyle); }); }); + + describe('getPointStyleOverrides', () => { + let mockAccessor: jest.Mock; + + const datum: DataSeriesDatum = { + x: 1, + y1: 2, + y0: 3, + initialY1: 4, + initialY0: 5, + }; + const geometryId: GeometryId = { + specId: getSpecId('test'), + seriesKey: ['test'], + }; + + beforeEach(() => { + mockAccessor = jest.fn(); + }); + + it('should return undefined if no pointStyleAccessor is passed', () => { + const styleOverrides = getPointStyleOverrides(datum, geometryId); + + expect(styleOverrides).toBeUndefined(); + }); + + it('should return undefined if pointStyleAccessor returns null', () => { + mockAccessor.mockReturnValue(null); + const styleOverrides = getPointStyleOverrides(datum, geometryId, mockAccessor); + + expect(styleOverrides).toBeUndefined(); + }); + + it('should call pointStyleAccessor with datum and geometryId', () => { + getPointStyleOverrides(datum, geometryId, mockAccessor); + + expect(mockAccessor).toBeCalledWith(datum, geometryId); + }); + + it('should return seriesStyle with updated stroke color', () => { + const stroke = 'blue'; + mockAccessor.mockReturnValue(stroke); + const styleOverrides = getPointStyleOverrides(datum, geometryId, mockAccessor); + const expectedStyles: Partial = { + stroke, + }; + expect(styleOverrides).toEqual(expectedStyles); + }); + }); }); diff --git a/src/chart_types/xy_chart/rendering/rendering.ts b/src/chart_types/xy_chart/rendering/rendering.ts index 87cd78c288..4a85824c51 100644 --- a/src/chart_types/xy_chart/rendering/rendering.ts +++ b/src/chart_types/xy_chart/rendering/rendering.ts @@ -17,7 +17,7 @@ import { CurveType, getCurveFactory } from '../../../utils/curves'; import { LegendItem } from '../legend/legend'; import { DataSeriesDatum } from '../utils/series'; import { belongsToDataSeries } from '../utils/series_utils'; -import { DisplayValueSpec, StyleAccessor } from '../utils/specs'; +import { DisplayValueSpec, BarStyleAccessor, PointStyleAccessor } from '../utils/specs'; import { mergePartial } from '../../../utils/commons'; export interface GeometryId { @@ -54,6 +54,7 @@ export interface PointGeometry { }; geometryId: GeometryId; value: GeometryValue; + styleOverrides?: Partial; } export interface BarGeometry { x: number; @@ -121,11 +122,31 @@ export function mutableIndexedGeometryMapUpsert( } } -export function getStyleOverrides( +export function getPointStyleOverrides( + datum: DataSeriesDatum, + geometryId: GeometryId, + pointStyleAccessor?: PointStyleAccessor, +): Partial | undefined { + const styleOverride = pointStyleAccessor && pointStyleAccessor(datum, geometryId); + + if (!styleOverride) { + return; + } + + if (typeof styleOverride === 'string') { + return { + stroke: styleOverride, + }; + } + + return styleOverride; +} + +export function getBarStyleOverrides( datum: DataSeriesDatum, geometryId: GeometryId, seriesStyle: BarSeriesStyle, - styleAccessor?: StyleAccessor, + styleAccessor?: BarStyleAccessor, ): BarSeriesStyle { const styleOverride = styleAccessor && styleAccessor(datum, geometryId); @@ -157,6 +178,7 @@ export function renderPoints( specId: SpecId, hasY0Accessors: boolean, seriesKey: any[], + styleAccessor?: PointStyleAccessor, ): { pointGeometries: PointGeometry[]; indexedGeometries: Map; @@ -191,6 +213,11 @@ export function renderPoints( y = yScale.scale(yDatum); } const originalY = hasY0Accessors && index === 0 ? datum.initialY0 : datum.initialY1; + const geometryId = { + specId, + seriesKey, + }; + const styleOverrides = getPointStyleOverrides(datum, geometryId, styleAccessor); const pointGeometry: PointGeometry = { radius, x, @@ -205,10 +232,8 @@ export function renderPoints( x: shift, y: 0, }, - geometryId: { - specId, - seriesKey, - }, + geometryId, + styleOverrides, }; mutableIndexedGeometryMapUpsert(indexedGeometries, datum.x, pointGeometry); // use the geometry only if the yDatum in contained in the current yScale domain @@ -236,7 +261,7 @@ export function renderBars( seriesKey: any[], sharedSeriesStyle: BarSeriesStyle, displayValueSettings?: DisplayValueSpec, - styleAccessor?: StyleAccessor, + styleAccessor?: BarStyleAccessor, ): { barGeometries: BarGeometry[]; indexedGeometries: Map; @@ -321,7 +346,7 @@ export function renderBars( seriesKey, }; - const seriesStyle = getStyleOverrides(datum, geometryId, sharedSeriesStyle, styleAccessor); + const seriesStyle = getBarStyleOverrides(datum, geometryId, sharedSeriesStyle, styleAccessor); const barGeometry: BarGeometry = { displayValue, @@ -362,6 +387,7 @@ export function renderLine( seriesKey: any[], xScaleOffset: number, seriesStyle: LineSeriesStyle, + pointStyleAccessor?: PointStyleAccessor, ): { lineGeometry: LineGeometry; indexedGeometries: Map; @@ -387,7 +413,9 @@ export function renderLine( specId, hasY0Accessors, seriesKey, + pointStyleAccessor, ); + const lineGeometry = { line: pathGenerator(dataset) || '', points: pointGeometries, @@ -422,6 +450,7 @@ export function renderArea( xScaleOffset: number, seriesStyle: AreaSeriesStyle, isStacked: boolean = false, + pointStyleAccessor?: PointStyleAccessor, ): { areaGeometry: AreaGeometry; indexedGeometries: Map; @@ -464,6 +493,7 @@ export function renderArea( specId, hasY0Accessors, seriesKey, + pointStyleAccessor, ); const areaGeometry = { diff --git a/src/chart_types/xy_chart/store/chart_state.interactions.test.ts b/src/chart_types/xy_chart/store/chart_state.interactions.test.ts index 0ef4c9dea1..61b6e94b58 100644 --- a/src/chart_types/xy_chart/store/chart_state.interactions.test.ts +++ b/src/chart_types/xy_chart/store/chart_state.interactions.test.ts @@ -290,7 +290,6 @@ function mouseOverTestSuite(scaleType: ScaleType) { store.annotationSpecs.set(rectAnnotationSpec.annotationId, rectAnnotationSpec); store.annotationDimensions.set(rectAnnotationSpec.annotationId, annotationDimensions); - debugger; // isHighlighted false, chart tooltip true; should show annotationTooltip only store.setCursorPosition(chartLeft + 51, chartTop + 1); expect(store.isTooltipVisible.get()).toBe(false); diff --git a/src/chart_types/xy_chart/store/utils.ts b/src/chart_types/xy_chart/store/utils.ts index 317d9f798e..151b51cca7 100644 --- a/src/chart_types/xy_chart/store/utils.ts +++ b/src/chart_types/xy_chart/store/utils.ts @@ -478,6 +478,7 @@ export function renderGeometries( ds.key, xScaleOffset, lineSeriesStyle, + spec.pointStyleAccessor, ); lineGeometriesIndex = mergeGeometriesIndexes(lineGeometriesIndex, renderedLines.indexedGeometries); lines.push(renderedLines.lineGeometry); @@ -504,6 +505,7 @@ export function renderGeometries( xScaleOffset, areaSeriesStyle, isStacked, + spec.pointStyleAccessor, ); areaGeometriesIndex = mergeGeometriesIndexes(areaGeometriesIndex, renderedAreas.indexedGeometries); areas.push(renderedAreas.areaGeometry); diff --git a/src/chart_types/xy_chart/utils/specs.ts b/src/chart_types/xy_chart/utils/specs.ts index 1e6b776951..45d2055d6a 100644 --- a/src/chart_types/xy_chart/utils/specs.ts +++ b/src/chart_types/xy_chart/utils/specs.ts @@ -5,6 +5,7 @@ import { LineSeriesStyle, RectAnnotationStyle, BarSeriesStyle, + PointStyle, } from '../../../utils/themes/theme'; import { Accessor } from '../../../utils/accessor'; import { Omit, RecursivePartial } from '../../../utils/commons'; @@ -19,8 +20,26 @@ export type Datum = any; export type Rotation = 0 | 90 | -90 | 180; export type Rendering = 'canvas' | 'svg'; export type Color = string; -export type StyleOverride = RecursivePartial | Color | null; -export type StyleAccessor = (datum: RawDataSeriesDatum, geometryId: GeometryId) => StyleOverride; +export type BarStyleOverride = RecursivePartial | Color | null; +export type PointStyleOverride = RecursivePartial | Color | null; +/** + * Override for bar styles per datum + * + * Return types: + * - `Color`: Color value as a `string` will set the bar `fill` to that color + * - `RecursivePartial`: Style values to be merged with base bar styles + * - `null`: Keep existing bar style + */ +export type BarStyleAccessor = (datum: RawDataSeriesDatum, geometryId: GeometryId) => BarStyleOverride; +/** + * Override for bar styles per datum + * + * Return types: + * - `Color`: Color value as a `string` will set the point `stroke` to that color + * - `RecursivePartial`: Style values to be merged with base point styles + * - `null`: Keep existing point style + */ +export type PointStyleAccessor = (datum: RawDataSeriesDatum, geometryId: GeometryId) => PointStyleOverride; export const DEFAULT_GLOBAL_ID = '__global__'; interface DomainMinInterval { @@ -104,8 +123,6 @@ export interface SeriesAccessors { splitSeriesAccessors?: Accessor[]; /** An array of fields thats indicates the stack membership */ stackAccessors?: Accessor[]; - /** An optional functional accessor to return custom datum color or style */ - styleAccessor?: StyleAccessor; } export interface SeriesScales { @@ -148,6 +165,10 @@ export type BarSeriesSpec = BasicSeriesSpec & { * Stack each series in percentage for each point. */ stackAsPercentage?: boolean; + /** + * An optional functional accessor to return custom color or style for bar datum + */ + styleAccessor?: BarStyleAccessor; }; /** @@ -167,6 +188,10 @@ export type LineSeriesSpec = BasicSeriesSpec & seriesType: 'line'; curve?: CurveType; lineSeriesStyle?: RecursivePartial; + /** + * An optional functional accessor to return custom color or style for point datum + */ + pointStyleAccessor?: PointStyleAccessor; }; /** @@ -183,6 +208,10 @@ export type AreaSeriesSpec = BasicSeriesSpec & * Stack each series in percentage for each point. */ stackAsPercentage?: boolean; + /** + * An optional functional accessor to return custom color or style for point datum + */ + pointStyleAccessor?: PointStyleAccessor; }; interface HistogramConfig { diff --git a/src/components/react_canvas/area_geometries.tsx b/src/components/react_canvas/area_geometries.tsx index b6c9b66565..df4b0869e4 100644 --- a/src/components/react_canvas/area_geometries.tsx +++ b/src/components/react_canvas/area_geometries.tsx @@ -9,7 +9,7 @@ import { getGeometryIdKey, GeometryId, } from '../../chart_types/xy_chart/rendering/rendering'; -import { SharedGeometryStyle } from '../../utils/themes/theme'; +import { SharedGeometryStyle, PointStyle } from '../../utils/themes/theme'; import { buildAreaRenderProps, buildPointStyleProps, @@ -17,6 +17,7 @@ import { PointStyleProps, buildLineRenderProps, } from './utils/rendering_props_utils'; +import { mergePartial } from '../../utils/commons'; interface AreaGeometriesDataProps { animated?: boolean; @@ -103,6 +104,14 @@ export class AreaGeometries extends React.PureComponent): PointStyleProps { + if (!overrides) { + return props; + } + + return mergePartial(props, overrides); + } + private renderPoints = ( areaPoints: PointGeometry[], areaIndex: number, @@ -110,9 +119,10 @@ export class AreaGeometries extends React.PureComponent { return areaPoints.map((areaPoint, pointIndex) => { - const { x, y, transform } = areaPoint; + const { x, y, transform, styleOverrides } = areaPoint; const key = getGeometryIdKey(geometryId, `area-point-${areaIndex}-${pointIndex}-`); - const pointProps = buildPointRenderProps(transform.x + x, y, pointStyleProps); + const pointStyle = this.mergePointPropsWithOverrides(pointStyleProps, styleOverrides); + const pointProps = buildPointRenderProps(transform.x + x, y, pointStyle); return ; }); }; diff --git a/src/components/react_canvas/line_geometries.tsx b/src/components/react_canvas/line_geometries.tsx index d6865c2035..70df65becf 100644 --- a/src/components/react_canvas/line_geometries.tsx +++ b/src/components/react_canvas/line_geometries.tsx @@ -8,13 +8,14 @@ import { PointGeometry, getGeometryIdKey, } from '../../chart_types/xy_chart/rendering/rendering'; -import { SharedGeometryStyle } from '../../utils/themes/theme'; +import { SharedGeometryStyle, PointStyle } from '../../utils/themes/theme'; import { buildLineRenderProps, buildPointStyleProps, PointStyleProps, buildPointRenderProps, } from './utils/rendering_props_utils'; +import { mergePartial } from '../../utils/commons'; interface LineGeometriesDataProps { animated?: boolean; @@ -47,43 +48,53 @@ export class LineGeometries extends React.PureComponent): PointStyleProps { + if (!overrides) { + return props; + } + + return mergePartial(props, overrides); + } + private renderPoints = ( linePoints: PointGeometry[], lineKey: string, pointStyleProps: PointStyleProps, ): JSX.Element[] => { - const linePointsElements: JSX.Element[] = []; + const linePointElements: JSX.Element[] = []; linePoints.forEach((linePoint, pointIndex) => { - const { x, y, transform } = linePoint; + const { x, y, transform, styleOverrides } = linePoint; const key = `line-point-${lineKey}-${pointIndex}`; - const pointProps = buildPointRenderProps(transform.x + x, y, pointStyleProps); - linePointsElements.push(); + const pointStyle = this.mergePointPropsWithOverrides(pointStyleProps, styleOverrides); + const pointProps = buildPointRenderProps(transform.x + x, y, pointStyle); + linePointElements.push(); }); - return linePointsElements; + return linePointElements; }; private renderLineGeoms = (): JSX.Element[] => { const { lines, sharedStyle } = this.props; - return lines.reduce((acc, glyph) => { - const { seriesLineStyle, seriesPointStyle, geometryId } = glyph; + return lines.reduce((acc, line) => { + const { seriesLineStyle, seriesPointStyle, geometryId } = line; const key = getGeometryIdKey(geometryId, 'line-'); if (seriesLineStyle.visible) { - acc.push(this.getLineToRender(glyph, sharedStyle, key)); + acc.push(this.getLineToRender(line, sharedStyle, key)); } if (seriesPointStyle.visible) { - acc.push(...this.getPointToRender(glyph, sharedStyle, key)); + acc.push(...this.getPointToRender(line, sharedStyle, key)); } + return acc; }, []); }; - getLineToRender(glyph: LineGeometry, sharedStyle: SharedGeometryStyle, key: string) { + getLineToRender(line: LineGeometry, sharedStyle: SharedGeometryStyle, key: string) { const { clippings } = this.props; - const { line, color, transform, geometryId, seriesLineStyle } = glyph; + const { line: linePath, color, transform, geometryId, seriesLineStyle } = line; const geometryStyle = getGeometryStyle(geometryId, this.props.highlightedLegendItem, sharedStyle); - const lineProps = buildLineRenderProps(transform.x, line, color, seriesLineStyle, geometryStyle); + const lineProps = buildLineRenderProps(transform.x, linePath, color, seriesLineStyle, geometryStyle); return ( @@ -91,8 +102,8 @@ export class LineGeometries extends React.PureComponent ); - }) - .add('styleAccessor overrides', () => { - const hasThreshold = boolean('threshold', true); - const threshold = number('min threshold', 4); - const style: RecursivePartial = { - rect: { - opacity: 0.5, - fill: 'red', - }, - }; - const styleAccessor: StyleAccessor = (d, g) => (g.specId === getSpecId('bars') && d.y1! > threshold ? style : null); - - return ( - - - Number(d).toFixed(2)} - /> - - - - ); }); diff --git a/stories/styling.tsx b/stories/styling.tsx index 86f4c41275..f8f4b17a2b 100644 --- a/stories/styling.tsx +++ b/stories/styling.tsx @@ -25,9 +25,12 @@ import { Theme, LIGHT_THEME, DARK_THEME, + BarSeriesStyle, + PointStyle, } from '../src/'; import * as TestDatasets from '../src/utils/data_samples/test_dataset'; import { palettes } from '../src/utils/themes/colors'; +import { BarStyleAccessor, PointStyleAccessor } from '../src/chart_types/xy_chart/utils/specs'; function range(title: string, min: number, max: number, value: number, groupId?: string, step: number = 1) { return number( @@ -858,4 +861,73 @@ storiesOf('Stylings', module) /> ); + }) + .add('Style Accessor Overrides', () => { + const hasThreshold = boolean('threshold', true); + const threshold = number('min threshold', 3); + const barStyle: RecursivePartial = { + rect: { + opacity: 0.5, + fill: 'red', + }, + }; + const pointStyle: RecursivePartial = { + fill: 'red', + radius: 10, + }; + const barStyleAccessor: BarStyleAccessor = (d, g) => + g.specId === getSpecId('bar') && d.y1! > threshold ? barStyle : null; + const pointStyleAccessor: PointStyleAccessor = (d, g) => + (g.specId === getSpecId('line') || g.specId === getSpecId('area')) && d.y1! > threshold ? pointStyle : null; + + return ( + + + + Number(d).toFixed(2)} + /> + + + + + + + + ); }); diff --git a/wiki/overview.md b/wiki/overview.md index e8fe1d8882..e1b016f75f 100644 --- a/wiki/overview.md +++ b/wiki/overview.md @@ -216,13 +216,38 @@ In the case of small multiples, each `SplittedSeries` computes its own x and y d ### Color/Style overrides -Each `datum` of a **Bar Chart** data series can be assigned a custom color or style with the `styleAccessor` prop. +#### BarSeries +Each bar `datum` of a **Bar** data series can be assigned a custom color or style with the `styleAccessor` prop. The `styleAccessor` prop expects a callback function which will be called on _every_ `datum` in _every_ bar series with the signiture below. This callback should return a color `string` or a partial `BarSeriesStyle`, which will override any series-level styles for the respective `datum`. You are passed `geometryId` to identify the series the `datum` belongs to and the raw `datum` to derive conditions against. +Return types: +- `Color`: Color value as a `string` will set the bar `fill` to that color +- `RecursivePartial`: Style values to be merged with base bar styles +- `null`: Keep existing bar style + ```ts -type StyleAccessor = ( +type BarStyleAccessor = ( datum: RawDataSeriesDatum, geometryId: GeometryId, ) => RecursivePartial | Color | null; ``` + +#### LineSeries and AreaSeries points +Each point `datum` of a **Line** or **Area** data series can be assigned a custom color or style with the `pointStyleAccessor` prop. + +The `pointStyleAccessor` prop expects a callback function which will be called on _every_ `datum` in _every_ line or area series with the signiture below. This callback should return a color `string` or a partial `PointStyle`, which will override any series-level styles for the respective `datum`. You are passed `geometryId` to identify the series the `datum` belongs to and the raw `datum` to derive conditions against. + +Return types: +- `Color`: Color value as a `string` will set the point `stroke` to that color +- `RecursivePartial`: Style values to be merged with base point styles +- `null`: Keep existing point style + +```ts +type PointStyleAccessor = ( + datum: RawDataSeriesDatum, + geometryId: GeometryId, +) => RecursivePartial | Color | null; +``` + +> Note: When overriding bar or point styles be mindful of performance and these accessor functions will be call on every bar/point is every series. Precomputing any expensive task before rendering.