mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[Lens] Synchronize cursor position for X-axis across all Lens visualizations in a dashboard (#106845)
* [Lens] Synchronize cursor position for X-axis across all Lens visualizations in a dashboard Closes: #77530 * add mocks for active_cursor service * fix jest tests * fix jest tests * apply PR comments * fix cursor style * update heatmap, jest * add tests * fix wrong import * replace cursor for timelion * update tsvb_dashboard baseline * fix CI * update baseline * Update active_cursor_utils.ts * add debounce * remove cursor from heatmap and pie * add tests for debounce * return theme order back Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
2605bd81cf
commit
f62a0a1f76
31 changed files with 657 additions and 93 deletions
|
@ -15,6 +15,8 @@ export { ChartsPluginSetup, ChartsPluginStart } from './plugin';
|
|||
export * from './static';
|
||||
export * from './services/palettes/types';
|
||||
export { lightenColor } from './services/palettes/lighten_color';
|
||||
export { useActiveCursor } from './services/active_cursor';
|
||||
|
||||
export {
|
||||
PaletteOutput,
|
||||
CustomPaletteArguments,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import { ChartsPlugin } from './plugin';
|
||||
import { themeServiceMock } from './services/theme/mock';
|
||||
import { activeCursorMock } from './services/active_cursor/mock';
|
||||
import { colorsServiceMock } from './services/legacy_colors/mock';
|
||||
import { getPaletteRegistry, paletteServiceMock } from './services/palettes/mock';
|
||||
|
||||
|
@ -23,6 +24,7 @@ const createSetupContract = (): Setup => ({
|
|||
const createStartContract = (): Start => ({
|
||||
legacyColors: colorsServiceMock,
|
||||
theme: themeServiceMock,
|
||||
activeCursor: activeCursorMock,
|
||||
palettes: paletteServiceMock.setup({} as any),
|
||||
});
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import { palette, systemPalette } from '../common';
|
|||
|
||||
import { ThemeService, LegacyColorsService } from './services';
|
||||
import { PaletteService } from './services/palettes/service';
|
||||
import { ActiveCursor } from './services/active_cursor';
|
||||
|
||||
export type Theme = Omit<ThemeService, 'init'>;
|
||||
export type Color = Omit<LegacyColorsService, 'init'>;
|
||||
|
@ -28,13 +29,16 @@ export interface ChartsPluginSetup {
|
|||
}
|
||||
|
||||
/** @public */
|
||||
export type ChartsPluginStart = ChartsPluginSetup;
|
||||
export type ChartsPluginStart = ChartsPluginSetup & {
|
||||
activeCursor: ActiveCursor;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export class ChartsPlugin implements Plugin<ChartsPluginSetup, ChartsPluginStart> {
|
||||
private readonly themeService = new ThemeService();
|
||||
private readonly legacyColorsService = new LegacyColorsService();
|
||||
private readonly paletteService = new PaletteService();
|
||||
private readonly activeCursor = new ActiveCursor();
|
||||
|
||||
private palettes: undefined | ReturnType<PaletteService['setup']>;
|
||||
|
||||
|
@ -45,6 +49,8 @@ export class ChartsPlugin implements Plugin<ChartsPluginSetup, ChartsPluginStart
|
|||
this.legacyColorsService.init(core.uiSettings);
|
||||
this.palettes = this.paletteService.setup(this.legacyColorsService);
|
||||
|
||||
this.activeCursor.setup();
|
||||
|
||||
return {
|
||||
legacyColors: this.legacyColorsService,
|
||||
theme: this.themeService,
|
||||
|
@ -57,6 +63,7 @@ export class ChartsPlugin implements Plugin<ChartsPluginSetup, ChartsPluginStart
|
|||
legacyColors: this.legacyColorsService,
|
||||
theme: this.themeService,
|
||||
palettes: this.palettes!,
|
||||
activeCursor: this.activeCursor,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ActiveCursor } from './active_cursor';
|
||||
|
||||
describe('ActiveCursor', () => {
|
||||
let activeCursor: ActiveCursor;
|
||||
|
||||
beforeEach(() => {
|
||||
activeCursor = new ActiveCursor();
|
||||
});
|
||||
|
||||
test('should initialize activeCursor$ stream on setup hook', () => {
|
||||
expect(activeCursor.activeCursor$).toBeUndefined();
|
||||
|
||||
activeCursor.setup();
|
||||
|
||||
expect(activeCursor.activeCursor$).toMatchInlineSnapshot(`
|
||||
Subject {
|
||||
"_isScalar": false,
|
||||
"closed": false,
|
||||
"hasError": false,
|
||||
"isStopped": false,
|
||||
"observers": Array [],
|
||||
"thrownError": null,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -7,6 +7,12 @@
|
|||
*/
|
||||
|
||||
import { Subject } from 'rxjs';
|
||||
import { PointerEvent } from '@elastic/charts';
|
||||
import type { ActiveCursorPayload } from './types';
|
||||
|
||||
export const activeCursor$ = new Subject<PointerEvent>();
|
||||
export class ActiveCursor {
|
||||
public activeCursor$?: Subject<ActiveCursorPayload>;
|
||||
|
||||
setup() {
|
||||
this.activeCursor$ = new Subject();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { parseSyncOptions } from './active_cursor_utils';
|
||||
import type { Datatable } from '../../../../expressions/public';
|
||||
|
||||
describe('active_cursor_utils', () => {
|
||||
describe('parseSyncOptions', () => {
|
||||
describe('dateHistogramSyncOption', () => {
|
||||
test('should return isDateHistogram true in case if that mode is active', () => {
|
||||
expect(parseSyncOptions({ isDateHistogram: true })).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"isDateHistogram": true,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('should return isDateHistogram false for other cases', () => {
|
||||
expect(parseSyncOptions({ datatables: [] as Datatable[] })).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"accessors": Array [],
|
||||
"isDateHistogram": false,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('datatablesSyncOption', () => {
|
||||
test('should extract accessors', () => {
|
||||
expect(
|
||||
parseSyncOptions({
|
||||
datatables: ([
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
meta: {
|
||||
index: 'foo_index',
|
||||
field: 'foo_field',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
] as unknown) as Datatable[],
|
||||
}).accessors
|
||||
).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"foo_index:foo_field",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('should return isDateHistogram true in case all datatables is time based', () => {
|
||||
expect(
|
||||
parseSyncOptions({
|
||||
datatables: ([
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
meta: {
|
||||
index: 'foo_index',
|
||||
field: 'foo_field',
|
||||
sourceParams: {
|
||||
appliedTimeRange: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
meta: {
|
||||
index: 'foo_index1',
|
||||
field: 'foo_field1',
|
||||
sourceParams: {
|
||||
appliedTimeRange: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
] as unknown) as Datatable[],
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"accessors": Array [
|
||||
"foo_index:foo_field",
|
||||
"foo_index1:foo_field1",
|
||||
],
|
||||
"isDateHistogram": true,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('should return isDateHistogram false in case of not all datatables is time based', () => {
|
||||
expect(
|
||||
parseSyncOptions({
|
||||
datatables: ([
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
meta: {
|
||||
index: 'foo_index',
|
||||
field: 'foo_field',
|
||||
sourceParams: {
|
||||
appliedTimeRange: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
meta: {
|
||||
index: 'foo_index1',
|
||||
field: 'foo_field1',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
] as unknown) as Datatable[],
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"accessors": Array [
|
||||
"foo_index:foo_field",
|
||||
"foo_index1:foo_field1",
|
||||
],
|
||||
"isDateHistogram": false,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { uniq } from 'lodash';
|
||||
|
||||
import type { Datatable } from '../../../../expressions/public';
|
||||
import type { ActiveCursorSyncOption, DateHistogramSyncOption } from './types';
|
||||
import type { ActiveCursorPayload } from './types';
|
||||
|
||||
function isDateHistogramSyncOption(
|
||||
syncOption?: ActiveCursorSyncOption
|
||||
): syncOption is DateHistogramSyncOption {
|
||||
return Boolean(syncOption && 'isDateHistogram' in syncOption);
|
||||
}
|
||||
|
||||
const parseDatatable = (dataTables: Datatable[]) => {
|
||||
const isDateHistogram =
|
||||
Boolean(dataTables.length) &&
|
||||
dataTables.every((dataTable) =>
|
||||
dataTable.columns.some((c) => Boolean(c.meta.sourceParams?.appliedTimeRange))
|
||||
);
|
||||
|
||||
const accessors = uniq(
|
||||
dataTables
|
||||
.map((dataTable) => {
|
||||
const column = dataTable.columns.find((c) => c.meta.index && c.meta.field);
|
||||
|
||||
if (column?.meta.index) {
|
||||
return `${column.meta.index}:${column.meta.field}`;
|
||||
}
|
||||
})
|
||||
.filter(Boolean) as string[]
|
||||
);
|
||||
return { isDateHistogram, accessors };
|
||||
};
|
||||
|
||||
/** @internal **/
|
||||
export const parseSyncOptions = (
|
||||
syncOptions: ActiveCursorSyncOption
|
||||
): Partial<ActiveCursorPayload> =>
|
||||
isDateHistogramSyncOption(syncOptions)
|
||||
? {
|
||||
isDateHistogram: syncOptions.isDateHistogram,
|
||||
}
|
||||
: parseDatatable(syncOptions.datatables);
|
|
@ -6,7 +6,5 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Subject } from 'rxjs';
|
||||
import { PointerEvent } from '@elastic/charts';
|
||||
|
||||
export const activeCursor$ = new Subject<PointerEvent>();
|
||||
export { ActiveCursor } from './active_cursor';
|
||||
export { useActiveCursor } from './use_active_cursor';
|
19
src/plugins/charts/public/services/active_cursor/mock.ts
Normal file
19
src/plugins/charts/public/services/active_cursor/mock.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { ActiveCursor } from './active_cursor';
|
||||
|
||||
export const activeCursorMock: ActiveCursor = {
|
||||
activeCursor$: {
|
||||
subscribe: jest.fn(),
|
||||
pipe: jest.fn(() => ({
|
||||
subscribe: jest.fn(),
|
||||
})),
|
||||
},
|
||||
setup: jest.fn(),
|
||||
} as any;
|
35
src/plugins/charts/public/services/active_cursor/types.ts
Normal file
35
src/plugins/charts/public/services/active_cursor/types.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { PointerEvent } from '@elastic/charts';
|
||||
import type { Datatable } from '../../../../expressions/public';
|
||||
|
||||
/** @public **/
|
||||
export type ActiveCursorSyncOption = DateHistogramSyncOption | DatatablesSyncOption;
|
||||
|
||||
/** @internal **/
|
||||
export interface ActiveCursorPayload {
|
||||
cursor: PointerEvent;
|
||||
isDateHistogram?: boolean;
|
||||
accessors?: string[];
|
||||
}
|
||||
|
||||
/** @internal **/
|
||||
interface BaseSyncOptions {
|
||||
debounce?: number;
|
||||
}
|
||||
|
||||
/** @internal **/
|
||||
export interface DateHistogramSyncOption extends BaseSyncOptions {
|
||||
isDateHistogram: boolean;
|
||||
}
|
||||
|
||||
/** @internal **/
|
||||
export interface DatatablesSyncOption extends BaseSyncOptions {
|
||||
datatables: Datatable[];
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import type { RefObject } from 'react';
|
||||
|
||||
import { ActiveCursor } from './active_cursor';
|
||||
import { useActiveCursor } from './use_active_cursor';
|
||||
|
||||
import type { ActiveCursorSyncOption, ActiveCursorPayload } from './types';
|
||||
import type { Chart, PointerEvent } from '@elastic/charts';
|
||||
import type { Datatable } from '../../../../expressions/public';
|
||||
|
||||
describe('useActiveCursor', () => {
|
||||
let cursor: ActiveCursorPayload['cursor'];
|
||||
let dispatchExternalPointerEvent: jest.Mock;
|
||||
|
||||
const act = (
|
||||
syncOption: ActiveCursorSyncOption,
|
||||
events: Array<Partial<ActiveCursorPayload>>,
|
||||
eventsTimeout = 1
|
||||
) =>
|
||||
new Promise(async (resolve) => {
|
||||
const activeCursor = new ActiveCursor();
|
||||
let allEventsExecuted = false;
|
||||
|
||||
activeCursor.setup();
|
||||
|
||||
dispatchExternalPointerEvent.mockImplementation((pointerEvent) => {
|
||||
if (allEventsExecuted) {
|
||||
resolve(pointerEvent);
|
||||
}
|
||||
});
|
||||
|
||||
renderHook(() =>
|
||||
useActiveCursor(
|
||||
activeCursor,
|
||||
{
|
||||
current: {
|
||||
dispatchExternalPointerEvent: dispatchExternalPointerEvent as (
|
||||
pointerEvent: PointerEvent
|
||||
) => void,
|
||||
},
|
||||
} as RefObject<Chart>,
|
||||
{ ...syncOption, debounce: syncOption.debounce ?? 1 }
|
||||
)
|
||||
);
|
||||
|
||||
for (const e of events) {
|
||||
await new Promise((eventResolve) =>
|
||||
setTimeout(() => {
|
||||
if (e === events[events.length - 1]) {
|
||||
allEventsExecuted = true;
|
||||
}
|
||||
activeCursor.activeCursor$!.next({ cursor, ...e });
|
||||
eventResolve(null);
|
||||
}, eventsTimeout)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cursor = {} as ActiveCursorPayload['cursor'];
|
||||
dispatchExternalPointerEvent = jest.fn();
|
||||
});
|
||||
|
||||
test('should debounce events', async () => {
|
||||
await act(
|
||||
{
|
||||
debounce: 5,
|
||||
datatables: [
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
meta: {
|
||||
index: 'foo_index',
|
||||
field: 'foo_field',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
] as Datatable[],
|
||||
},
|
||||
[
|
||||
{ accessors: ['foo_index:foo_field'] },
|
||||
{ accessors: ['foo_index:foo_field'] },
|
||||
{ accessors: ['foo_index:foo_field'] },
|
||||
{ accessors: ['foo_index:foo_field'] },
|
||||
]
|
||||
);
|
||||
|
||||
expect(dispatchExternalPointerEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should trigger cursor pointer update (chart type: time, event type: time)', async () => {
|
||||
await act({ isDateHistogram: true }, [{ isDateHistogram: true }]);
|
||||
|
||||
expect(dispatchExternalPointerEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should trigger cursor pointer update (chart type: datatable - time based, event type: time)', async () => {
|
||||
await act(
|
||||
{
|
||||
datatables: ([
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
meta: {
|
||||
index: 'foo_index',
|
||||
field: 'foo_field',
|
||||
sourceParams: {
|
||||
appliedTimeRange: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
] as unknown) as Datatable[],
|
||||
},
|
||||
[{ isDateHistogram: true }, { accessors: ['foo_index:foo_field'] }]
|
||||
);
|
||||
|
||||
expect(dispatchExternalPointerEvent).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('should not trigger cursor pointer update (chart type: datatable, event type: time)', async () => {
|
||||
await act(
|
||||
{
|
||||
datatables: [
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
meta: {
|
||||
index: 'foo_index',
|
||||
field: 'foo_field',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
] as Datatable[],
|
||||
},
|
||||
[{ isDateHistogram: true }, { accessors: ['foo_index:foo_field'] }]
|
||||
);
|
||||
|
||||
expect(dispatchExternalPointerEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should works with multi datatables (intersection)', async () => {
|
||||
await act(
|
||||
{
|
||||
datatables: [
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
meta: {
|
||||
index: 'ia',
|
||||
field: 'fa',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
meta: {
|
||||
index: 'ib',
|
||||
field: 'fb',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
] as Datatable[],
|
||||
},
|
||||
[{ accessors: ['foo_index:foo_field', 'ib:fb'] }]
|
||||
);
|
||||
|
||||
expect(dispatchExternalPointerEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { intersection } from 'lodash';
|
||||
import { animationFrameScheduler } from 'rxjs';
|
||||
import { useCallback, useEffect, RefObject } from 'react';
|
||||
import { filter, debounceTime } from 'rxjs/operators';
|
||||
|
||||
import type { Chart } from '@elastic/charts';
|
||||
|
||||
import { parseSyncOptions } from './active_cursor_utils';
|
||||
|
||||
import type { ActiveCursor } from './active_cursor';
|
||||
import type { ActiveCursorSyncOption } from './types';
|
||||
|
||||
const DEFAULT_DEBOUNCE_TIME = 40;
|
||||
|
||||
export const useActiveCursor = (
|
||||
activeCursor: ActiveCursor,
|
||||
chartRef: RefObject<Chart>,
|
||||
syncOptions: ActiveCursorSyncOption
|
||||
) => {
|
||||
const { accessors, isDateHistogram } = parseSyncOptions(syncOptions);
|
||||
const handleCursorUpdate = useCallback(
|
||||
(cursor) => {
|
||||
activeCursor.activeCursor$?.next({
|
||||
cursor,
|
||||
isDateHistogram,
|
||||
accessors,
|
||||
});
|
||||
},
|
||||
[activeCursor.activeCursor$, accessors, isDateHistogram]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const cursorSubscription = activeCursor.activeCursor$
|
||||
?.pipe(
|
||||
debounceTime(syncOptions.debounce ?? DEFAULT_DEBOUNCE_TIME, animationFrameScheduler),
|
||||
filter((payload) => {
|
||||
if (payload.isDateHistogram && isDateHistogram) {
|
||||
return true;
|
||||
}
|
||||
return intersection(accessors, payload.accessors).length > 0;
|
||||
})
|
||||
)
|
||||
.subscribe(({ cursor }) => {
|
||||
chartRef?.current?.dispatchExternalPointerEvent(cursor);
|
||||
});
|
||||
|
||||
return () => {
|
||||
cursorSubscription?.unsubscribe();
|
||||
};
|
||||
}, [activeCursor.activeCursor$, syncOptions.debounce, chartRef, accessors, isDateHistogram]);
|
||||
|
||||
return handleCursorUpdate;
|
||||
};
|
|
@ -8,3 +8,4 @@
|
|||
|
||||
export { LegacyColorsService } from './legacy_colors';
|
||||
export { ThemeService } from './theme';
|
||||
export { ActiveCursor, useActiveCursor } from './active_cursor';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useCallback, useMemo, useRef } from 'react';
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import { compact, last, map } from 'lodash';
|
||||
import {
|
||||
Chart,
|
||||
|
@ -14,13 +14,13 @@ import {
|
|||
Position,
|
||||
Axis,
|
||||
TooltipType,
|
||||
PointerEvent,
|
||||
LegendPositionConfig,
|
||||
LayoutDirection,
|
||||
} from '@elastic/charts';
|
||||
import { EuiTitle } from '@elastic/eui';
|
||||
|
||||
import { useKibana } from '../../../kibana_react/public';
|
||||
import { useActiveCursor } from '../../../charts/public';
|
||||
|
||||
import { AreaSeriesComponent, BarSeriesComponent } from './series';
|
||||
|
||||
|
@ -33,7 +33,7 @@ import {
|
|||
} from '../helpers/panel_utils';
|
||||
|
||||
import { colors } from '../helpers/chart_constants';
|
||||
import { activeCursor$ } from '../helpers/active_cursor';
|
||||
import { getCharts } from '../helpers/plugin_services';
|
||||
|
||||
import type { Sheet } from '../helpers/timelion_request_handler';
|
||||
import type { IInterpreterRenderHandlers } from '../../../expressions';
|
||||
|
@ -100,20 +100,14 @@ const TimelionVisComponent = ({
|
|||
const kibana = useKibana<TimelionVisDependencies>();
|
||||
const chartRef = useRef<Chart>(null);
|
||||
const chart = seriesList.list;
|
||||
const chartsService = getCharts();
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = activeCursor$.subscribe((cursor: PointerEvent) => {
|
||||
chartRef.current?.dispatchExternalPointerEvent(cursor);
|
||||
});
|
||||
const chartTheme = chartsService.theme.useChartsTheme();
|
||||
const chartBaseTheme = chartsService.theme.useChartsBaseTheme();
|
||||
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleCursorUpdate = useCallback((cursor: PointerEvent) => {
|
||||
activeCursor$.next(cursor);
|
||||
}, []);
|
||||
const handleCursorUpdate = useActiveCursor(chartsService.activeCursor, chartRef, {
|
||||
isDateHistogram: true,
|
||||
});
|
||||
|
||||
const brushEndListener = useCallback(
|
||||
({ x }) => {
|
||||
|
@ -198,8 +192,8 @@ const TimelionVisComponent = ({
|
|||
legendPosition={legend.legendPosition}
|
||||
onRenderChange={onRenderChange}
|
||||
onPointerUpdate={handleCursorUpdate}
|
||||
theme={kibana.services.chartTheme.useChartsTheme()}
|
||||
baseTheme={kibana.services.chartTheme.useChartsBaseTheme()}
|
||||
theme={chartTheme}
|
||||
baseTheme={chartBaseTheme}
|
||||
tooltip={{
|
||||
snap: true,
|
||||
headerFormatter: ({ value }) => tickFormat(value),
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import type { IndexPatternsContract, ISearchStart } from 'src/plugins/data/public';
|
||||
import type { ChartsPluginStart } from 'src/plugins/charts/public';
|
||||
import { createGetterSetter } from '../../../kibana_utils/public';
|
||||
|
||||
export const [getIndexPatterns, setIndexPatterns] = createGetterSetter<IndexPatternsContract>(
|
||||
|
@ -14,3 +15,5 @@ export const [getIndexPatterns, setIndexPatterns] = createGetterSetter<IndexPatt
|
|||
);
|
||||
|
||||
export const [getDataSearch, setDataSearch] = createGetterSetter<ISearchStart>('Search');
|
||||
|
||||
export const [getCharts, setCharts] = createGetterSetter<ChartsPluginStart>('Charts');
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import {
|
||||
import type {
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
Plugin,
|
||||
|
@ -14,30 +14,29 @@ import {
|
|||
IUiSettingsClient,
|
||||
HttpSetup,
|
||||
} from 'kibana/public';
|
||||
import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public';
|
||||
import {
|
||||
import type { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public';
|
||||
import type {
|
||||
DataPublicPluginSetup,
|
||||
DataPublicPluginStart,
|
||||
TimefilterContract,
|
||||
} from 'src/plugins/data/public';
|
||||
|
||||
import { VisualizationsSetup } from '../../visualizations/public';
|
||||
import { ChartsPluginSetup } from '../../charts/public';
|
||||
import type { VisualizationsSetup } from 'src/plugins/visualizations/public';
|
||||
import type { ChartsPluginSetup, ChartsPluginStart } from 'src/plugins/charts/public';
|
||||
|
||||
import { getTimelionVisualizationConfig } from './timelion_vis_fn';
|
||||
import { getTimelionVisDefinition } from './timelion_vis_type';
|
||||
import { setIndexPatterns, setDataSearch } from './helpers/plugin_services';
|
||||
import { ConfigSchema } from '../config';
|
||||
import { setIndexPatterns, setDataSearch, setCharts } from './helpers/plugin_services';
|
||||
|
||||
import { getArgValueSuggestions } from './helpers/arg_value_suggestions';
|
||||
import { getTimelionVisRenderer } from './timelion_vis_renderer';
|
||||
|
||||
import type { ConfigSchema } from '../config';
|
||||
|
||||
/** @internal */
|
||||
export interface TimelionVisDependencies extends Partial<CoreStart> {
|
||||
uiSettings: IUiSettingsClient;
|
||||
http: HttpSetup;
|
||||
timefilter: TimefilterContract;
|
||||
chartTheme: ChartsPluginSetup['theme'];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -51,6 +50,7 @@ export interface TimelionVisSetupDependencies {
|
|||
/** @internal */
|
||||
export interface TimelionVisStartDependencies {
|
||||
data: DataPublicPluginStart;
|
||||
charts: ChartsPluginStart;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
|
@ -82,7 +82,6 @@ export class TimelionVisPlugin
|
|||
http,
|
||||
uiSettings,
|
||||
timefilter: data.query.timefilter.timefilter,
|
||||
chartTheme: charts.theme,
|
||||
};
|
||||
|
||||
expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies));
|
||||
|
@ -94,9 +93,10 @@ export class TimelionVisPlugin
|
|||
};
|
||||
}
|
||||
|
||||
public start(core: CoreStart, plugins: TimelionVisStartDependencies) {
|
||||
setIndexPatterns(plugins.data.indexPatterns);
|
||||
setDataSearch(plugins.data.search);
|
||||
public start(core: CoreStart, { data, charts }: TimelionVisStartDependencies) {
|
||||
setIndexPatterns(data.indexPatterns);
|
||||
setDataSearch(data.search);
|
||||
setCharts(charts);
|
||||
|
||||
return {
|
||||
getArgValueSuggestions,
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
|
||||
import { SeriesConfigQueryBarWithIgnoreGlobalFilter } from '../../series_config_query_bar_with_ignore_global_filter';
|
||||
import { PalettePicker } from '../../palette_picker';
|
||||
import { getChartsSetup } from '../../../../services';
|
||||
import { getCharts } from '../../../../services';
|
||||
import { isPercentDisabled } from '../../lib/stacked';
|
||||
import { STACKED_OPTIONS } from '../../../visualizations/constants/chart';
|
||||
|
||||
|
@ -120,7 +120,7 @@ export const TimeseriesConfig = injectI18n(function (props) {
|
|||
const selectedChartTypeOption = chartTypeOptions.find((option) => {
|
||||
return model.chart_type === option.value;
|
||||
});
|
||||
const { palettes } = getChartsSetup();
|
||||
const { palettes } = getCharts();
|
||||
const [palettesRegistry, setPalettesRegistry] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useCallback } from 'react';
|
||||
import React, { useRef, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { labelDateFormatter } from '../../../components/lib/label_date_formatter';
|
||||
|
@ -23,8 +23,7 @@ import {
|
|||
} from '@elastic/charts';
|
||||
import { EuiIcon } from '@elastic/eui';
|
||||
import { getTimezone } from '../../../lib/get_timezone';
|
||||
import { activeCursor$ } from '../../lib/active_cursor';
|
||||
import { getUISettings, getChartsSetup } from '../../../../services';
|
||||
import { getUISettings, getCharts } from '../../../../services';
|
||||
import { GRID_LINE_CONFIG, ICON_TYPES_MAP, STACKED_OPTIONS } from '../../constants';
|
||||
import { AreaSeriesDecorator } from './decorators/area_decorator';
|
||||
import { BarSeriesDecorator } from './decorators/bar_decorator';
|
||||
|
@ -33,7 +32,7 @@ import { getBaseTheme, getChartClasses } from './utils/theme';
|
|||
import { TOOLTIP_MODES } from '../../../../../common/enums';
|
||||
import { getValueOrEmpty } from '../../../../../common/empty_label';
|
||||
import { getSplitByTermsColor } from '../../../lib/get_split_by_terms_color';
|
||||
import { renderEndzoneTooltip } from '../../../../../../charts/public';
|
||||
import { renderEndzoneTooltip, useActiveCursor } from '../../../../../../charts/public';
|
||||
import { getAxisLabelString } from '../../../components/lib/get_axis_label_string';
|
||||
import { calculateDomainForSeries } from './utils/series_domain_calculation';
|
||||
|
||||
|
@ -48,10 +47,6 @@ const generateAnnotationData = (values, formatter) =>
|
|||
|
||||
const decorateFormatter = (formatter) => ({ value }) => formatter(value);
|
||||
|
||||
const handleCursorUpdate = (cursor) => {
|
||||
activeCursor$.next(cursor);
|
||||
};
|
||||
|
||||
export const TimeSeries = ({
|
||||
backgroundColor,
|
||||
showGrid,
|
||||
|
@ -69,22 +64,17 @@ export const TimeSeries = ({
|
|||
interval,
|
||||
isLastBucketDropped,
|
||||
}) => {
|
||||
// If the color isn't configured by the user, use the color mapping service
|
||||
// to assign a color from the Kibana palette. Colors will be shared across the
|
||||
// session, including dashboards.
|
||||
const { theme: themeService, activeCursor: activeCursorService } = getCharts();
|
||||
|
||||
const chartRef = useRef();
|
||||
// const [palettesRegistry, setPalettesRegistry] = useState(null);
|
||||
const chartTheme = themeService.useChartsTheme();
|
||||
|
||||
useEffect(() => {
|
||||
const updateCursor = (cursor) => {
|
||||
if (chartRef.current) {
|
||||
chartRef.current.dispatchExternalPointerEvent(cursor);
|
||||
}
|
||||
};
|
||||
|
||||
const subscription = activeCursor$.subscribe(updateCursor);
|
||||
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
const handleCursorUpdate = useActiveCursor(activeCursorService, chartRef, {
|
||||
isDateHistogram: true,
|
||||
});
|
||||
|
||||
let tooltipFormatter = decorateFormatter(xAxisFormatter);
|
||||
if (!isLastBucketDropped) {
|
||||
|
@ -104,11 +94,6 @@ export const TimeSeries = ({
|
|||
// apply legend style change if bgColor is configured
|
||||
const classes = classNames(getChartClasses(backgroundColor));
|
||||
|
||||
// If the color isn't configured by the user, use the color mapping service
|
||||
// to assign a color from the Kibana palette. Colors will be shared across the
|
||||
// session, including dashboards.
|
||||
const { theme: themeService } = getChartsSetup();
|
||||
|
||||
const baseTheme = getBaseTheme(themeService.useChartsBaseTheme(), backgroundColor);
|
||||
|
||||
const onBrushEndListener = ({ x }) => {
|
||||
|
@ -152,6 +137,7 @@ export const TimeSeries = ({
|
|||
animateData={false}
|
||||
onPointerUpdate={handleCursorUpdate}
|
||||
theme={[
|
||||
chartTheme,
|
||||
hasBarChart
|
||||
? {}
|
||||
: {
|
||||
|
|
|
@ -20,23 +20,23 @@ import {
|
|||
setFieldFormats,
|
||||
setCoreStart,
|
||||
setDataStart,
|
||||
setChartsSetup,
|
||||
setCharts,
|
||||
} from './services';
|
||||
import { DataPublicPluginStart } from '../../data/public';
|
||||
import { ChartsPluginSetup } from '../../charts/public';
|
||||
import { ChartsPluginStart } from '../../charts/public';
|
||||
import { getTimeseriesVisRenderer } from './timeseries_vis_renderer';
|
||||
|
||||
/** @internal */
|
||||
export interface MetricsPluginSetupDependencies {
|
||||
expressions: ReturnType<ExpressionsPublicPlugin['setup']>;
|
||||
visualizations: VisualizationsSetup;
|
||||
charts: ChartsPluginSetup;
|
||||
visualize: VisualizePluginSetup;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface MetricsPluginStartDependencies {
|
||||
data: DataPublicPluginStart;
|
||||
charts: ChartsPluginStart;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -49,7 +49,7 @@ export class MetricsPlugin implements Plugin<void, void> {
|
|||
|
||||
public setup(
|
||||
core: CoreSetup,
|
||||
{ expressions, visualizations, charts, visualize }: MetricsPluginSetupDependencies
|
||||
{ expressions, visualizations, visualize }: MetricsPluginSetupDependencies
|
||||
) {
|
||||
visualize.visEditorsRegistry.register(TSVB_EDITOR_NAME, EditorController);
|
||||
expressions.registerFunction(createMetricsFn);
|
||||
|
@ -59,11 +59,11 @@ export class MetricsPlugin implements Plugin<void, void> {
|
|||
})
|
||||
);
|
||||
setUISettings(core.uiSettings);
|
||||
setChartsSetup(charts);
|
||||
visualizations.createBaseVisualization(metricsVisDefinition);
|
||||
}
|
||||
|
||||
public start(core: CoreStart, { data }: MetricsPluginStartDependencies) {
|
||||
public start(core: CoreStart, { data, charts }: MetricsPluginStartDependencies) {
|
||||
setCharts(charts);
|
||||
setI18n(core.i18n);
|
||||
setFieldFormats(data.fieldFormats);
|
||||
setDataStart(data);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import { I18nStart, IUiSettingsClient, CoreStart } from 'src/core/public';
|
||||
import { createGetterSetter } from '../../kibana_utils/public';
|
||||
import { ChartsPluginSetup } from '../../charts/public';
|
||||
import { ChartsPluginStart } from '../../charts/public';
|
||||
import { DataPublicPluginStart } from '../../data/public';
|
||||
|
||||
export const [getUISettings, setUISettings] = createGetterSetter<IUiSettingsClient>('UISettings');
|
||||
|
@ -23,6 +23,4 @@ export const [getDataStart, setDataStart] = createGetterSetter<DataPublicPluginS
|
|||
|
||||
export const [getI18n, setI18n] = createGetterSetter<I18nStart>('I18n');
|
||||
|
||||
export const [getChartsSetup, setChartsSetup] = createGetterSetter<ChartsPluginSetup>(
|
||||
'ChartsPluginSetup'
|
||||
);
|
||||
export const [getCharts, setCharts] = createGetterSetter<ChartsPluginStart>('ChartsPluginStart');
|
||||
|
|
|
@ -17,7 +17,7 @@ import { VisualizationContainer, PersistedState } from '../../visualizations/pub
|
|||
|
||||
import type { TimeseriesVisData } from '../common/types';
|
||||
import { isVisTableData } from '../common/vis_data_utils';
|
||||
import { getChartsSetup } from './services';
|
||||
import { getCharts } from './services';
|
||||
|
||||
import type { TimeseriesVisParams } from './types';
|
||||
import type { ExpressionRenderDefinition } from '../../expressions/common';
|
||||
|
@ -49,7 +49,7 @@ export const getTimeseriesVisRenderer: (deps: {
|
|||
handlers.onDestroy(() => {
|
||||
unmountComponentAtNode(domNode);
|
||||
});
|
||||
const { palettes } = getChartsSetup();
|
||||
const { palettes } = getCharts();
|
||||
const showNoResult = !checkIfDataExists(config.visData, config.visParams);
|
||||
const palettesService = await palettes.getPalettes();
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import React, { FC } from 'react';
|
|||
import {
|
||||
Direction,
|
||||
Settings,
|
||||
SettingsSpecProps,
|
||||
DomainRange,
|
||||
Position,
|
||||
PartialTheme,
|
||||
|
@ -49,6 +50,7 @@ type XYSettingsProps = Pick<
|
|||
| 'xAxis'
|
||||
| 'orderBucketsBySum'
|
||||
> & {
|
||||
onPointerUpdate: SettingsSpecProps['onPointerUpdate'];
|
||||
xDomain?: DomainRange;
|
||||
adjustedXDomain?: DomainRange;
|
||||
showLegend: boolean;
|
||||
|
@ -85,6 +87,7 @@ export const XYSettings: FC<XYSettingsProps> = ({
|
|||
adjustedXDomain,
|
||||
showLegend,
|
||||
onElementClick,
|
||||
onPointerUpdate,
|
||||
onBrushEnd,
|
||||
onRenderChange,
|
||||
legendAction,
|
||||
|
@ -107,6 +110,9 @@ export const XYSettings: FC<XYSettingsProps> = ({
|
|||
barSeriesStyle: {
|
||||
...valueLabelsStyling,
|
||||
},
|
||||
crosshair: {
|
||||
...theme.crosshair,
|
||||
},
|
||||
axes: {
|
||||
axisTitle: {
|
||||
padding: {
|
||||
|
@ -152,6 +158,7 @@ export const XYSettings: FC<XYSettingsProps> = ({
|
|||
return (
|
||||
<Settings
|
||||
debugState={window._echDebugStateFlag ?? false}
|
||||
onPointerUpdate={onPointerUpdate}
|
||||
xDomain={adjustedXDomain}
|
||||
rotation={rotation}
|
||||
theme={[themeOverrides, theme]}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
import { CoreSetup, CoreStart, Plugin } from '../../../core/public';
|
||||
import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public';
|
||||
import { VisualizationsSetup, VisualizationsStart } from '../../visualizations/public';
|
||||
import { ChartsPluginSetup } from '../../charts/public';
|
||||
import { ChartsPluginSetup, ChartsPluginStart } from '../../charts/public';
|
||||
import { DataPublicPluginStart } from '../../data/public';
|
||||
import { UsageCollectionSetup } from '../../usage_collection/public';
|
||||
import {
|
||||
|
@ -20,6 +20,7 @@ import {
|
|||
setDocLinks,
|
||||
setPalettesService,
|
||||
setTrackUiMetric,
|
||||
setActiveCursor,
|
||||
} from './services';
|
||||
|
||||
import { visTypesDefinitions } from './vis_types';
|
||||
|
@ -46,6 +47,7 @@ export interface VisTypeXyPluginStartDependencies {
|
|||
expressions: ReturnType<ExpressionsPublicPlugin['start']>;
|
||||
visualizations: VisualizationsStart;
|
||||
data: DataPublicPluginStart;
|
||||
charts: ChartsPluginStart;
|
||||
}
|
||||
|
||||
type VisTypeXyCoreSetup = CoreSetup<VisTypeXyPluginStartDependencies, VisTypeXyPluginStart>;
|
||||
|
@ -86,11 +88,11 @@ export class VisTypeXyPlugin
|
|||
return {};
|
||||
}
|
||||
|
||||
public start(core: CoreStart, { data }: VisTypeXyPluginStartDependencies) {
|
||||
public start(core: CoreStart, { data, charts }: VisTypeXyPluginStartDependencies) {
|
||||
setFormatService(data.fieldFormats);
|
||||
setDataActions(data.actions);
|
||||
setDocLinks(core.docLinks);
|
||||
|
||||
setActiveCursor(charts.activeCursor);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import { UiCounterMetricType } from '@kbn/analytics';
|
|||
import { CoreSetup, DocLinksStart } from '../../../core/public';
|
||||
import { createGetterSetter } from '../../kibana_utils/public';
|
||||
import { DataPublicPluginStart } from '../../data/public';
|
||||
import { ChartsPluginSetup } from '../../charts/public';
|
||||
import { ChartsPluginSetup, ChartsPluginStart } from '../../charts/public';
|
||||
|
||||
export const [getUISettings, setUISettings] = createGetterSetter<CoreSetup['uiSettings']>(
|
||||
'xy core.uiSettings'
|
||||
|
@ -28,6 +28,10 @@ export const [getThemeService, setThemeService] = createGetterSetter<ChartsPlugi
|
|||
'xy charts.theme'
|
||||
);
|
||||
|
||||
export const [getActiveCursor, setActiveCursor] = createGetterSetter<
|
||||
ChartsPluginStart['activeCursor']
|
||||
>('xy charts.activeCursor');
|
||||
|
||||
export const [getPalettesService, setPalettesService] = createGetterSetter<
|
||||
ChartsPluginSetup['palettes']
|
||||
>('xy charts.palette');
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import {
|
||||
Chart,
|
||||
|
@ -32,7 +32,7 @@ import {
|
|||
} from '../../charts/public';
|
||||
import { Datatable, IInterpreterRenderHandlers } from '../../expressions/public';
|
||||
import type { PersistedState } from '../../visualizations/public';
|
||||
|
||||
import { useActiveCursor } from '../../charts/public';
|
||||
import { VisParams } from './types';
|
||||
import {
|
||||
getAdjustedDomain,
|
||||
|
@ -47,7 +47,7 @@ import {
|
|||
} from './utils';
|
||||
import { XYAxis, XYEndzones, XYCurrentTime, XYSettings, XYThresholdLine } from './components';
|
||||
import { getConfig } from './config';
|
||||
import { getThemeService, getDataActions, getPalettesService } from './services';
|
||||
import { getThemeService, getDataActions, getPalettesService, getActiveCursor } from './services';
|
||||
import { ChartType } from '../common';
|
||||
|
||||
import './_chart.scss';
|
||||
|
@ -77,6 +77,11 @@ const VisComponent = (props: VisComponentProps) => {
|
|||
return props.uiState?.get('vis.legendOpen', bwcLegendStateDefault) as boolean;
|
||||
});
|
||||
const [palettesRegistry, setPalettesRegistry] = useState<PaletteRegistry | null>(null);
|
||||
const chartRef = useRef<Chart>(null);
|
||||
|
||||
const handleCursorUpdate = useActiveCursor(getActiveCursor(), chartRef, {
|
||||
datatables: [props.visData],
|
||||
});
|
||||
|
||||
const onRenderChange = useCallback<RenderChangeListener>(
|
||||
(isRendered) => {
|
||||
|
@ -333,7 +338,7 @@ const VisComponent = (props: VisComponentProps) => {
|
|||
showLegend={showLegend}
|
||||
legendPosition={legendPosition}
|
||||
/>
|
||||
<Chart size="100%">
|
||||
<Chart size="100%" ref={chartRef}>
|
||||
<ChartSplitter
|
||||
splitColumnAccessor={splitChartColumnAccessor}
|
||||
splitRowAccessor={splitChartRowAccessor}
|
||||
|
@ -341,6 +346,7 @@ const VisComponent = (props: VisComponentProps) => {
|
|||
<XYSettings
|
||||
{...config}
|
||||
showLegend={showLegend}
|
||||
onPointerUpdate={handleCursorUpdate}
|
||||
legendPosition={legendPosition}
|
||||
xDomain={xDomain}
|
||||
adjustedXDomain={adjustedXDomain}
|
||||
|
|
|
@ -172,7 +172,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
// click on specific coordinates
|
||||
await browser
|
||||
.getActions()
|
||||
.move({ x: 100, y: 110, origin: el._webElement })
|
||||
.move({ x: 105, y: 110, origin: el._webElement })
|
||||
.click()
|
||||
.perform();
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 71 KiB |
|
@ -17,6 +17,7 @@ exports[`xy_expression XYChart component it renders area 1`] = `
|
|||
legendPosition="top"
|
||||
onBrushEnd={[Function]}
|
||||
onElementClick={[Function]}
|
||||
onPointerUpdate={[Function]}
|
||||
rotation={0}
|
||||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
|
@ -234,6 +235,7 @@ exports[`xy_expression XYChart component it renders bar 1`] = `
|
|||
legendPosition="top"
|
||||
onBrushEnd={[Function]}
|
||||
onElementClick={[Function]}
|
||||
onPointerUpdate={[Function]}
|
||||
rotation={0}
|
||||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
|
@ -465,6 +467,7 @@ exports[`xy_expression XYChart component it renders horizontal bar 1`] = `
|
|||
legendPosition="top"
|
||||
onBrushEnd={[Function]}
|
||||
onElementClick={[Function]}
|
||||
onPointerUpdate={[Function]}
|
||||
rotation={90}
|
||||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
|
@ -696,6 +699,7 @@ exports[`xy_expression XYChart component it renders line 1`] = `
|
|||
legendPosition="top"
|
||||
onBrushEnd={[Function]}
|
||||
onElementClick={[Function]}
|
||||
onPointerUpdate={[Function]}
|
||||
rotation={0}
|
||||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
|
@ -913,6 +917,7 @@ exports[`xy_expression XYChart component it renders stacked area 1`] = `
|
|||
legendPosition="top"
|
||||
onBrushEnd={[Function]}
|
||||
onElementClick={[Function]}
|
||||
onPointerUpdate={[Function]}
|
||||
rotation={0}
|
||||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
|
@ -1138,6 +1143,7 @@ exports[`xy_expression XYChart component it renders stacked bar 1`] = `
|
|||
legendPosition="top"
|
||||
onBrushEnd={[Function]}
|
||||
onElementClick={[Function]}
|
||||
onPointerUpdate={[Function]}
|
||||
rotation={0}
|
||||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
|
@ -1377,6 +1383,7 @@ exports[`xy_expression XYChart component it renders stacked horizontal bar 1`] =
|
|||
legendPosition="top"
|
||||
onBrushEnd={[Function]}
|
||||
onElementClick={[Function]}
|
||||
onPointerUpdate={[Function]}
|
||||
rotation={90}
|
||||
showLegend={false}
|
||||
showLegendExtra={false}
|
||||
|
|
|
@ -49,7 +49,12 @@ import { XyEndzones } from './x_domain';
|
|||
const onClickValue = jest.fn();
|
||||
const onSelectRange = jest.fn();
|
||||
|
||||
const chartsThemeService = chartPluginMock.createSetupContract().theme;
|
||||
const chartSetupContract = chartPluginMock.createSetupContract();
|
||||
const chartStartContract = chartPluginMock.createStartContract();
|
||||
|
||||
const chartsThemeService = chartSetupContract.theme;
|
||||
const chartsActiveCursorService = chartStartContract.activeCursor;
|
||||
|
||||
const paletteService = chartPluginMock.createPaletteRegistry();
|
||||
|
||||
const mockPaletteOutput: PaletteOutput = {
|
||||
|
@ -473,6 +478,7 @@ describe('xy_expression', () => {
|
|||
timeZone: 'UTC',
|
||||
renderMode: 'display',
|
||||
chartsThemeService,
|
||||
chartsActiveCursorService,
|
||||
paletteService,
|
||||
minInterval: 50,
|
||||
onClickValue,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import './expression.scss';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import {
|
||||
Chart,
|
||||
|
@ -47,8 +47,10 @@ import { isHorizontalChart, getSeriesColor } from './state_helpers';
|
|||
import { search } from '../../../../../src/plugins/data/public';
|
||||
import {
|
||||
ChartsPluginSetup,
|
||||
ChartsPluginStart,
|
||||
PaletteRegistry,
|
||||
SeriesLayer,
|
||||
useActiveCursor,
|
||||
} from '../../../../../src/plugins/charts/public';
|
||||
import { EmptyPlaceholder } from '../shared_components';
|
||||
import { getFitOptions } from './fitting_functions';
|
||||
|
@ -85,6 +87,7 @@ export {
|
|||
|
||||
export type XYChartRenderProps = XYChartProps & {
|
||||
chartsThemeService: ChartsPluginSetup['theme'];
|
||||
chartsActiveCursorService: ChartsPluginStart['activeCursor'];
|
||||
paletteService: PaletteRegistry;
|
||||
formatFactory: FormatFactory;
|
||||
timeZone: string;
|
||||
|
@ -121,7 +124,8 @@ export function calculateMinInterval({ args: { layers }, data }: XYChartProps) {
|
|||
|
||||
export const getXyChartRenderer = (dependencies: {
|
||||
formatFactory: Promise<FormatFactory>;
|
||||
chartsThemeService: ChartsPluginSetup['theme'];
|
||||
chartsThemeService: ChartsPluginStart['theme'];
|
||||
chartsActiveCursorService: ChartsPluginStart['activeCursor'];
|
||||
paletteService: PaletteRegistry;
|
||||
timeZone: string;
|
||||
}): ExpressionRenderDefinition<XYChartProps> => ({
|
||||
|
@ -150,6 +154,7 @@ export const getXyChartRenderer = (dependencies: {
|
|||
<XYChartReportable
|
||||
{...config}
|
||||
formatFactory={formatFactory}
|
||||
chartsActiveCursorService={dependencies.chartsActiveCursorService}
|
||||
chartsThemeService={dependencies.chartsThemeService}
|
||||
paletteService={dependencies.paletteService}
|
||||
timeZone={dependencies.timeZone}
|
||||
|
@ -222,6 +227,7 @@ export function XYChart({
|
|||
formatFactory,
|
||||
timeZone,
|
||||
chartsThemeService,
|
||||
chartsActiveCursorService,
|
||||
paletteService,
|
||||
minInterval,
|
||||
onClickValue,
|
||||
|
@ -240,11 +246,16 @@ export function XYChart({
|
|||
yRightExtent,
|
||||
valuesInLegend,
|
||||
} = args;
|
||||
const chartRef = useRef<Chart>(null);
|
||||
const chartTheme = chartsThemeService.useChartsTheme();
|
||||
const chartBaseTheme = chartsThemeService.useChartsBaseTheme();
|
||||
const darkMode = chartsThemeService.useDarkMode();
|
||||
const filteredLayers = getFilteredLayers(layers, data);
|
||||
|
||||
const handleCursorUpdate = useActiveCursor(chartsActiveCursorService, chartRef, {
|
||||
datatables: Object.values(data.tables),
|
||||
});
|
||||
|
||||
if (filteredLayers.length === 0) {
|
||||
const icon: IconType = layers.length > 0 ? getIconForSeriesType(layers[0].seriesType) : 'bar';
|
||||
return <EmptyPlaceholder icon={icon} />;
|
||||
|
@ -486,8 +497,9 @@ export function XYChart({
|
|||
} as LegendPositionConfig;
|
||||
|
||||
return (
|
||||
<Chart>
|
||||
<Chart ref={chartRef}>
|
||||
<Settings
|
||||
onPointerUpdate={handleCursorUpdate}
|
||||
debugState={window._echDebugStateFlag ?? false}
|
||||
showLegend={
|
||||
legend.isVisible && !legend.showSingleSeries
|
||||
|
|
|
@ -25,7 +25,7 @@ export class XyVisualization {
|
|||
|
||||
setup(
|
||||
core: CoreSetup<LensPluginStartDependencies, void>,
|
||||
{ expressions, formatFactory, editorFrame, charts }: XyVisualizationPluginSetupPlugins
|
||||
{ expressions, formatFactory, editorFrame }: XyVisualizationPluginSetupPlugins
|
||||
) {
|
||||
editorFrame.registerVisualization(async () => {
|
||||
const {
|
||||
|
@ -41,7 +41,7 @@ export class XyVisualization {
|
|||
getXyChartRenderer,
|
||||
getXyVisualization,
|
||||
} = await import('../async_services');
|
||||
const [, { data }] = await core.getStartServices();
|
||||
const [, { data, charts }] = await core.getStartServices();
|
||||
const palettes = await charts.palettes.getPalettes();
|
||||
expressions.registerFunction(() => legendConfig);
|
||||
expressions.registerFunction(() => yAxisConfig);
|
||||
|
@ -57,6 +57,7 @@ export class XyVisualization {
|
|||
getXyChartRenderer({
|
||||
formatFactory,
|
||||
chartsThemeService: charts.theme,
|
||||
chartsActiveCursorService: charts.activeCursor,
|
||||
paletteService: palettes,
|
||||
timeZone: getTimeZone(core.uiSettings),
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue