@@ -27,7 +27,7 @@ import {
2727} from '@elastic/charts' ;
2828
2929import { RangeSelectContext , ValueClickContext } from '../../../../embeddable/public' ;
30- import { Datatable } from '../../../../expressions/common/expression_types/specs ' ;
30+ import { Datatable } from '../../../../expressions/public ' ;
3131
3232export interface ClickTriggerEvent {
3333 name : 'filterBucket' ;
@@ -39,6 +39,13 @@ export interface BrushTriggerEvent {
3939 data : RangeSelectContext [ 'data' ] ;
4040}
4141
42+ type AllSeriesAccessors = Array < [ accessor : Accessor | AccessorFn , value : string | number ] > ;
43+
44+ /**
45+ * returns accessor value from string or function accessor
46+ * @param datum
47+ * @param accessor
48+ */
4249function getAccessorValue ( datum : Datum , accessor : Accessor | AccessorFn ) {
4350 if ( typeof accessor === 'function' ) {
4451 return accessor ( datum ) ;
@@ -52,8 +59,12 @@ function getAccessorValue(datum: Datum, accessor: Accessor | AccessorFn) {
5259 * difficult to match the correct column. This creates a test object to throw
5360 * an error when the target id is accessed, thus matcing the target column.
5461 */
55- function validateFnAccessorId ( id : string , accessor : AccessorFn ) {
56- const matchedMessage = 'validateFnAccessorId matched' ;
62+ function validateAccessorId ( id : string , accessor : Accessor | AccessorFn ) {
63+ if ( typeof accessor !== 'function' ) {
64+ return id === accessor ;
65+ }
66+
67+ const matchedMessage = 'validateAccessorId matched' ;
5768
5869 try {
5970 accessor ( {
@@ -67,58 +78,100 @@ function validateFnAccessorId(id: string, accessor: AccessorFn) {
6778 }
6879}
6980
81+ /**
82+ * Groups split accessors by their accessor string or function and related value
83+ *
84+ * @param splitAccessors
85+ * @param splitSeriesAccessorFnMap
86+ */
87+ const getAllSplitAccessors = (
88+ splitAccessors : Map < string | number , string | number > ,
89+ splitSeriesAccessorFnMap ?: Map < string | number , AccessorFn >
90+ ) : Array < [ accessor : Accessor | AccessorFn , value : string | number ] > =>
91+ [ ...splitAccessors . entries ( ) ] . map ( ( [ key , value ] ) => [
92+ splitSeriesAccessorFnMap ?. get ?.( key ) ?? key ,
93+ value ,
94+ ] ) ;
95+
96+ /**
97+ * Reduces matching column indexes
98+ *
99+ * @param xAccessor
100+ * @param yAccessor
101+ * @param splitAccessors
102+ */
103+ const columnReducer = (
104+ xAccessor : Accessor | AccessorFn | null ,
105+ yAccessor : Accessor | AccessorFn | null ,
106+ splitAccessors : AllSeriesAccessors
107+ ) => ( acc : number [ ] , { id } : Datatable [ 'columns' ] [ number ] , index : number ) : number [ ] => {
108+ if (
109+ ( xAccessor !== null && validateAccessorId ( id , xAccessor ) ) ||
110+ ( yAccessor !== null && validateAccessorId ( id , yAccessor ) ) ||
111+ splitAccessors . some ( ( [ accessor ] ) => validateAccessorId ( id , accessor ) )
112+ ) {
113+ acc . push ( index ) ;
114+ }
115+
116+ return acc ;
117+ } ;
118+
119+ /**
120+ * Finds matching row index for given accessors and geometry values
121+ *
122+ * @param geometry
123+ * @param xAccessor
124+ * @param yAccessor
125+ * @param splitAccessors
126+ */
127+ const rowFindPredicate = (
128+ geometry : GeometryValue | null ,
129+ xAccessor : Accessor | AccessorFn | null ,
130+ yAccessor : Accessor | AccessorFn | null ,
131+ splitAccessors : AllSeriesAccessors
132+ ) => ( row : Datatable [ 'rows' ] [ number ] ) : boolean =>
133+ ( geometry === null ||
134+ ( xAccessor !== null &&
135+ getAccessorValue ( row , xAccessor ) === geometry . x &&
136+ yAccessor !== null &&
137+ getAccessorValue ( row , yAccessor ) === geometry . y ) ) &&
138+ [ ...splitAccessors ] . every ( ( [ accessor , value ] ) => getAccessorValue ( row , accessor ) === value ) ;
139+
70140/**
71141 * Helper function to transform `@elastic/charts` click event into filter action event
142+ *
143+ * @param table
144+ * @param xAccessor
145+ * @param splitSeriesAccessorFnMap needed when using `splitSeriesAccessors` as `AccessorFn`
146+ * @param negate
72147 */
73148export const getFilterFromChartClickEventFn = (
74149 table : Datatable ,
75150 xAccessor : Accessor | AccessorFn ,
151+ splitSeriesAccessorFnMap ?: Map < string | number , AccessorFn > ,
76152 negate : boolean = false
77153) => ( points : Array < [ GeometryValue , XYChartSeriesIdentifier ] > ) : ClickTriggerEvent => {
78154 const data : ValueClickContext [ 'data' ] [ 'data' ] = [ ] ;
79- const seenKeys = new Set < string > ( ) ;
80155
81156 points . forEach ( ( point ) => {
82157 const [ geometry , { yAccessor, splitAccessors } ] = point ;
83- const columnIndices = table . columns . reduce < number [ ] > ( ( acc , { id } , index ) => {
84- if (
85- ( typeof xAccessor === 'function' && validateFnAccessorId ( id , xAccessor ) ) ||
86- [ xAccessor , yAccessor , ...splitAccessors . keys ( ) ] . includes ( id )
87- ) {
88- acc . push ( index ) ;
89- }
90-
91- return acc ;
92- } , [ ] ) ;
93-
94- const rowIndex = table . rows . findIndex ( ( row ) => {
95- return (
96- getAccessorValue ( row , xAccessor ) === geometry . x &&
97- row [ yAccessor ] === geometry . y &&
98- [ ...splitAccessors . entries ( ) ] . every ( ( [ key , value ] ) => row [ key ] === value )
99- ) ;
100- } ) ;
101-
102- data . push (
103- ...columnIndices
104- . map ( ( column ) => ( {
105- table,
106- column,
107- row : rowIndex ,
108- value : null ,
109- } ) )
110- . filter ( ( column ) => {
111- // filter duplicate values when multiple geoms are highlighted
112- const key = `column:${ column } ,row:${ rowIndex } ` ;
113- if ( seenKeys . has ( key ) ) {
114- return false ;
115- }
116-
117- seenKeys . add ( key ) ;
118-
119- return true ;
120- } )
158+ const allSplitAccessors = getAllSplitAccessors ( splitAccessors , splitSeriesAccessorFnMap ) ;
159+ const columnIndices = table . columns . reduce < number [ ] > (
160+ columnReducer ( xAccessor , yAccessor , allSplitAccessors ) ,
161+ [ ]
162+ ) ;
163+ const row = table . rows . findIndex (
164+ rowFindPredicate ( geometry , xAccessor , yAccessor , allSplitAccessors )
121165 ) ;
166+ const value = getAccessorValue ( table . rows [ row ] , yAccessor ) ;
167+ const newData = columnIndices . map ( ( column ) => ( {
168+ table,
169+ column,
170+ row,
171+ value,
172+ } ) ) ;
173+
174+ data . push ( ...newData ) ;
122175 } ) ;
123176
124177 return {
@@ -135,22 +188,21 @@ export const getFilterFromChartClickEventFn = (
135188 */
136189export const getFilterFromSeriesFn = ( table : Datatable ) => (
137190 { splitAccessors } : XYChartSeriesIdentifier ,
191+ splitSeriesAccessorFnMap ?: Map < string | number , AccessorFn > ,
138192 negate = false
139193) : ClickTriggerEvent => {
140- const data = table . columns . reduce < ValueClickContext [ 'data' ] [ 'data' ] > ( ( acc , { id } , column ) => {
141- if ( [ ...splitAccessors . keys ( ) ] . includes ( id ) ) {
142- const value = splitAccessors . get ( id ) ;
143- const row = table . rows . findIndex ( ( r ) => r [ id ] === value ) ;
144- acc . push ( {
145- table,
146- column,
147- row,
148- value,
149- } ) ;
150- }
151-
152- return acc ;
153- } , [ ] ) ;
194+ const allSplitAccessors = getAllSplitAccessors ( splitAccessors , splitSeriesAccessorFnMap ) ;
195+ const columnIndices = table . columns . reduce < number [ ] > (
196+ columnReducer ( null , null , allSplitAccessors ) ,
197+ [ ]
198+ ) ;
199+ const row = table . rows . findIndex ( rowFindPredicate ( null , null , null , allSplitAccessors ) ) ;
200+ const data : ValueClickContext [ 'data' ] [ 'data' ] = columnIndices . map ( ( column ) => ( {
201+ table,
202+ column,
203+ row,
204+ value : null ,
205+ } ) ) ;
154206
155207 return {
156208 name : 'filterBucket' ,
@@ -170,7 +222,7 @@ export const getBrushFromChartBrushEventFn = (
170222) => ( { x : selectedRange } : XYBrushArea ) : BrushTriggerEvent => {
171223 const [ start , end ] = selectedRange ?? [ 0 , 0 ] ;
172224 const range : [ number , number ] = [ start , end ] ;
173- const column = table . columns . findIndex ( ( c ) => c . id === xAccessor ) ;
225+ const column = table . columns . findIndex ( ( { id } ) => validateAccessorId ( id , xAccessor ) ) ;
174226
175227 return {
176228 data : {
0 commit comments