diff --git a/api/charts.api.md b/api/charts.api.md index ea6014ba78..9971f96f6a 100644 --- a/api/charts.api.md +++ b/api/charts.api.md @@ -678,10 +678,10 @@ export type DisplayValueStyle = Omit & { export type DomainRange = LowerBoundedDomain | UpperBoundedDomain | CompleteBoundedDomain | UnboundedDomainWithInterval; // @public (undocumented) -export type ElementClickListener = (elements: Array) => void; +export type ElementClickListener = (elements: Array) => void; // @public (undocumented) -export type ElementOverListener = (elements: Array) => void; +export type ElementOverListener = (elements: Array) => void; // @public (undocumented) export const entryKey: ([key]: ArrayEntry) => string; @@ -1330,6 +1330,9 @@ export interface OrderBy { // @public (undocumented) export type OrdinalDomain = (number | string)[]; +// @public (undocumented) +export type OutOfRoomCallback = (wordCount: number, renderedWordCount: number, renderedWords: string[]) => void; + // Warning: (ae-forgotten-export) The symbol "PerSideDistance" needs to be exported by the entry point index.d.ts // // @public (undocumented) @@ -2114,12 +2117,58 @@ export interface Visible { visible: boolean; } +// @public (undocumented) +export const WeightFn: Readonly<{ + log: "log"; + linear: "linear"; + exponential: "exponential"; + squareRoot: "squareRoot"; +}>; + +// @public (undocumented) +export type WeightFn = $Values; + // Warning: (ae-forgotten-export) The symbol "SpecRequiredProps" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "SpecOptionalProps" needs to be exported by the entry point index.d.ts // // @alpha (undocumented) export const Wordcloud: React_2.FunctionComponent; +// @public (undocumented) +export interface WordcloudConfigs { + // (undocumented) + count: number; + // (undocumented) + endAngle: number; + // (undocumented) + exponent: number; + // (undocumented) + fontFamily: string; + // (undocumented) + fontStyle: string; + // (undocumented) + fontWeight: number; + // (undocumented) + height: number; + // (undocumented) + maxFontSize: number; + // (undocumented) + minFontSize: number; + // (undocumented) + padding: number; + // (undocumented) + spiral: string; + // (undocumented) + startAngle: number; + // (undocumented) + weightFn: WeightFn; + // (undocumented) + width: number; +} + +// @public (undocumented) +export type WordCloudElementEvent = [WordModel, SeriesIdentifier]; + // @alpha (undocumented) export interface WordcloudSpec extends Spec { // (undocumented) @@ -2127,9 +2176,7 @@ export interface WordcloudSpec extends Spec { // (undocumented) chartType: typeof ChartType.Wordcloud; // (undocumented) - config: RecursivePartial; - // Warning: (ae-forgotten-export) The symbol "WordModel" needs to be exported by the entry point index.d.ts - // + config: RecursivePartial; // (undocumented) data: WordModel[]; // (undocumented) @@ -2146,8 +2193,6 @@ export interface WordcloudSpec extends Spec { maxFontSize: number; // (undocumented) minFontSize: number; - // Warning: (ae-forgotten-export) The symbol "OutOfRoomCallback" needs to be exported by the entry point index.d.ts - // // (undocumented) outOfRoomCallback: OutOfRoomCallback; // (undocumented) @@ -2158,12 +2203,20 @@ export interface WordcloudSpec extends Spec { spiral: string; // (undocumented) startAngle: number; - // Warning: (ae-forgotten-export) The symbol "WeightFn" needs to be exported by the entry point index.d.ts - // // (undocumented) weightFn: WeightFn; } +// @public (undocumented) +export interface WordModel { + // (undocumented) + color: Color; + // (undocumented) + text: string; + // (undocumented) + weight: number; +} + // @public (undocumented) export type XScaleType = typeof ScaleType.Ordinal | ScaleContinuousType; diff --git a/src/chart_types/partition_chart/state/selectors/get_debug_state.test.ts b/src/chart_types/partition_chart/state/selectors/get_debug_state.test.ts index 712f52fd06..aea457bbd7 100644 --- a/src/chart_types/partition_chart/state/selectors/get_debug_state.test.ts +++ b/src/chart_types/partition_chart/state/selectors/get_debug_state.test.ts @@ -25,6 +25,7 @@ import { HeatmapElementEvent, LayerValue, PartitionElementEvent, + WordCloudElementEvent, XYChartElementEvent, } from '../../../../specs/settings'; import { onMouseDown, onMouseUp, onPointerMove } from '../../../../state/actions/mouse'; @@ -68,7 +69,7 @@ describe.each([ let store: Store; let onClickListener: jest.Mock< undefined, - Array<(XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent)[]> + Array<(XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent | WordCloudElementEvent)[]> >; let debugState: DebugState; @@ -113,7 +114,10 @@ describe.each([ function expectCorrectClickInfo( store: Store, - onClickListener: jest.Mock>, + onClickListener: jest.Mock< + undefined, + Array<(XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent | WordCloudElementEvent)[]> + >, partition: SinglePartitionDebugState, index: number, ) { diff --git a/src/chart_types/partition_chart/state/selectors/picked_shapes.test.ts b/src/chart_types/partition_chart/state/selectors/picked_shapes.test.ts index 684a19b92f..e21ee8c661 100644 --- a/src/chart_types/partition_chart/state/selectors/picked_shapes.test.ts +++ b/src/chart_types/partition_chart/state/selectors/picked_shapes.test.ts @@ -28,6 +28,7 @@ import { HeatmapElementEvent, GroupBySpec, SmallMultiplesSpec, + WordCloudElementEvent, } from '../../../../specs'; import { updateParentDimensions } from '../../../../state/actions/chart_settings'; import { onMouseDown, onMouseUp, onPointerMove } from '../../../../state/actions/mouse'; @@ -101,7 +102,7 @@ describe('Picked shapes selector', () => { test('treemap check picked geometries', () => { const onClickListener = jest.fn< undefined, - Array<(XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent)[]> + Array[] >((): undefined => undefined); addSeries(store, treemapSpec, { onElementClick: onClickListener, @@ -154,7 +155,7 @@ describe('Picked shapes selector', () => { test('small multiples pie chart check picked geometries', () => { const onClickListener = jest.fn< undefined, - Array<(XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent)[]> + Array[] >((): undefined => undefined); addSmallMultiplesSeries( store, @@ -222,7 +223,7 @@ describe('Picked shapes selector', () => { test('sunburst check picked geometries', () => { const onClickListener = jest.fn< undefined, - Array<(XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent)[]> + Array[] >((): undefined => undefined); addSeries(store, sunburstSpec, { onElementClick: onClickListener, diff --git a/src/chart_types/wordcloud/layout/types/viewmodel_types.ts b/src/chart_types/wordcloud/layout/types/viewmodel_types.ts index e204f432d8..94b1291e08 100644 --- a/src/chart_types/wordcloud/layout/types/viewmodel_types.ts +++ b/src/chart_types/wordcloud/layout/types/viewmodel_types.ts @@ -64,9 +64,10 @@ export interface Word { y0: number; y1: number; yoff: number; + datum: WordModel; } -/** @internal */ +/** @public */ export interface Configs { count: number; endAngle: number; @@ -122,6 +123,7 @@ export type ShapeViewModel = { wordcloudViewModel: WordcloudViewModel; chartCenter: PointObject; pickQuads: PickFunction; + specId: string; }; const commonDefaults: WordcloudViewModel = { @@ -158,4 +160,5 @@ export const nullShapeViewModel = (specifiedConfig?: Config, chartCenter?: Point wordcloudViewModel: nullWordcloudViewModel, chartCenter: chartCenter || { x: 0, y: 0 }, pickQuads: () => [], + specId: 'empty', }); diff --git a/src/chart_types/wordcloud/layout/viewmodel/viewmodel.ts b/src/chart_types/wordcloud/layout/viewmodel/viewmodel.ts index b51977fb48..009e429796 100644 --- a/src/chart_types/wordcloud/layout/viewmodel/viewmodel.ts +++ b/src/chart_types/wordcloud/layout/viewmodel/viewmodel.ts @@ -34,6 +34,7 @@ export function shapeViewModel(spec: WordcloudSpec, config: Config): ShapeViewMo }; const { + id, startAngle, endAngle, angleCount, @@ -78,5 +79,6 @@ export function shapeViewModel(spec: WordcloudSpec, config: Config): ShapeViewMo chartCenter, wordcloudViewModel, pickQuads, + specId: id, }; } diff --git a/src/chart_types/wordcloud/renderer/svg/connected_component.tsx b/src/chart_types/wordcloud/renderer/svg/connected_component.tsx index 46719d580c..4793825366 100644 --- a/src/chart_types/wordcloud/renderer/svg/connected_component.tsx +++ b/src/chart_types/wordcloud/renderer/svg/connected_component.tsx @@ -24,6 +24,7 @@ import { connect } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; import { ScreenReaderSummary } from '../../../../components/accessibility'; +import { SettingsSpec, WordCloudElementEvent } from '../../../../specs/settings'; import { onChartRendered } from '../../../../state/actions/chart'; import { GlobalChartState } from '../../../../state/chart_state'; import { @@ -32,6 +33,7 @@ import { getA11ySettingsSelector, } from '../../../../state/selectors/get_accessibility_config'; import { getInternalIsInitializedSelector, InitStatus } from '../../../../state/selectors/get_internal_is_intialized'; +import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs'; import { Dimensions } from '../../../../utils/dimensions'; import { Configs, Datum, nullShapeViewModel, ShapeViewModel, Word } from '../../layout/types/viewmodel_types'; import { geometries } from '../../state/selectors/geometries'; @@ -104,6 +106,7 @@ function layoutMaker(config: Configs, data: Datum[]) { const words = data.map((d) => { const weightFn = weightFnLookup[config.weightFn]; return { + datum: d, text: d.text, color: d.color, fontFamily: config.fontFamily, @@ -125,36 +128,74 @@ function layoutMaker(config: Configs, data: Datum[]) { .fontSize((d: Word) => getFontSize(d)); } -const View = ({ words, conf }: { words: Word[]; conf: Configs }) => ( - - - {words.map((d, i) => { - return ( - - {d.text} - - ); - })} - - -); +const View = ({ + words, + conf, + actions: { onElementClick, onElementOver, onElementOut }, + specId, +}: { + words: Word[]; + conf: Configs; + actions: { + onElementClick?: SettingsSpec['onElementClick']; + onElementOver?: SettingsSpec['onElementOver']; + onElementOut?: SettingsSpec['onElementOut']; + }; + specId: string; +}) => { + return ( + + + {words.map((d, i) => { + const elements: WordCloudElementEvent[] = [[d.datum, { specId, key: specId }]]; + const actions = { + ...(onElementClick && { + onClick: () => { + onElementClick(elements); + }, + }), + ...(onElementOver && { + onMouseOver: () => { + onElementOver(elements); + }, + }), + ...(onElementOut && { + onMouseOut: () => { + onElementOut(); + }, + }), + }; + return ( + + {d.text} + + ); + })} + + + ); +}; interface ReactiveChartStateProps { initialized: boolean; geometries: ShapeViewModel; chartContainerDimensions: Dimensions; a11ySettings: A11ySettings; + onElementClick?: SettingsSpec['onElementClick']; + onElementOver?: SettingsSpec['onElementOver']; + onElementOut?: SettingsSpec['onElementOut']; } interface ReactiveChartDispatchProps { @@ -182,8 +223,11 @@ class Component extends React.Component { const { initialized, chartContainerDimensions: { width, height }, - geometries: { wordcloudViewModel }, + geometries: { wordcloudViewModel, specId }, a11ySettings, + onElementClick, + onElementOver, + onElementOut, } = this.props; if (!initialized || width === 0 || height === 0) { return null; @@ -224,7 +268,12 @@ class Component extends React.Component { return (
- +
); @@ -260,6 +309,9 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { geometries: geometries(state), chartContainerDimensions: state.parentDimensions, a11ySettings: getA11ySettingsSelector(state), + onElementClick: getSettingsSpecSelector(state).onElementClick, + onElementOver: getSettingsSpecSelector(state).onElementOver, + onElementOut: getSettingsSpecSelector(state).onElementOut, }; }; diff --git a/src/chart_types/wordcloud/specs/index.ts b/src/chart_types/wordcloud/specs/index.ts index 4cc6d9f23d..4864b78588 100644 --- a/src/chart_types/wordcloud/specs/index.ts +++ b/src/chart_types/wordcloud/specs/index.ts @@ -24,9 +24,14 @@ import { Spec } from '../../../specs'; import { SpecType } from '../../../specs/constants'; import { getConnect, specComponentFactory } from '../../../state/spec_factory'; import { RecursivePartial } from '../../../utils/common'; -import { Config } from '../../partition_chart/layout/types/config_types'; import { config } from '../layout/config/config'; -import { WordModel, defaultWordcloudSpec, WeightFn, OutOfRoomCallback } from '../layout/types/viewmodel_types'; +import { + WordModel, + defaultWordcloudSpec, + WeightFn, + OutOfRoomCallback, + Configs as WordcloudConfigs, +} from '../layout/types/viewmodel_types'; const defaultProps = { chartType: ChartType.Wordcloud, @@ -35,11 +40,13 @@ const defaultProps = { config, }; +export { WordModel, WeightFn, OutOfRoomCallback, WordcloudConfigs }; + /** @alpha */ export interface WordcloudSpec extends Spec { specType: typeof SpecType.Series; chartType: typeof ChartType.Wordcloud; - config: RecursivePartial; + config: RecursivePartial; startAngle: number; endAngle: number; angleCount: number; diff --git a/src/chart_types/wordcloud/state/chart_state.tsx b/src/chart_types/wordcloud/state/chart_state.tsx index 34a6767ba4..91290c24d8 100644 --- a/src/chart_types/wordcloud/state/chart_state.tsx +++ b/src/chart_types/wordcloud/state/chart_state.tsx @@ -29,9 +29,6 @@ import { DebugState } from '../../../state/types'; import { Dimensions } from '../../../utils/dimensions'; import { EMPTY_TOOLTIP } from '../../partition_chart/layout/viewmodel/tooltip_info'; import { Wordcloud } from '../renderer/svg/connected_component'; -import { createOnElementClickCaller } from './selectors/on_element_click_caller'; -import { createOnElementOutCaller } from './selectors/on_element_out_caller'; -import { createOnElementOverCaller } from './selectors/on_element_over_caller'; import { getSpecOrNull } from './selectors/wordcloud_spec'; const EMPTY_MAP = new Map(); @@ -42,18 +39,6 @@ const EMPTY_LEGEND_ITEM_LIST: LegendItemLabel[] = []; export class WordcloudState implements InternalChartState { chartType = ChartType.Wordcloud; - onElementClickCaller: (state: GlobalChartState) => void; - - onElementOverCaller: (state: GlobalChartState) => void; - - onElementOutCaller: (state: GlobalChartState) => void; - - constructor() { - this.onElementClickCaller = createOnElementClickCaller(); - this.onElementOverCaller = createOnElementOverCaller(); - this.onElementOutCaller = createOnElementOutCaller(); - } - isInitialized(globalState: GlobalChartState) { return getSpecOrNull(globalState) !== null ? InitStatus.Initialized : InitStatus.ChartNotInitialized; } @@ -107,11 +92,7 @@ export class WordcloudState implements InternalChartState { }; } - eventCallbacks(globalState: GlobalChartState) { - this.onElementOverCaller(globalState); - this.onElementOutCaller(globalState); - this.onElementClickCaller(globalState); - } + eventCallbacks() {} getChartTypeDescription() { return 'Word cloud chart'; diff --git a/src/chart_types/wordcloud/state/selectors/scenegraph.ts b/src/chart_types/wordcloud/state/selectors/scenegraph.ts index 9575ce369a..2e97007f33 100644 --- a/src/chart_types/wordcloud/state/selectors/scenegraph.ts +++ b/src/chart_types/wordcloud/state/selectors/scenegraph.ts @@ -21,7 +21,7 @@ import { mergePartial, RecursivePartial } from '../../../../utils/common'; import { Dimensions } from '../../../../utils/dimensions'; import { config as defaultConfig } from '../../layout/config/config'; import { Config } from '../../layout/types/config_types'; -import { ShapeViewModel, nullShapeViewModel } from '../../layout/types/viewmodel_types'; +import { ShapeViewModel } from '../../layout/types/viewmodel_types'; import { shapeViewModel } from '../../layout/viewmodel/viewmodel'; import { WordcloudSpec } from '../../specs'; @@ -29,12 +29,7 @@ import { WordcloudSpec } from '../../specs'; export function render(spec: WordcloudSpec, parentDimensions: Dimensions): ShapeViewModel { const { width, height } = parentDimensions; const { config } = spec; - const textMeasurer = document.createElement('canvas'); - const textMeasurerCtx = textMeasurer.getContext('2d'); const partialConfig: RecursivePartial = { ...config, width, height }; const cfg: Config = mergePartial(defaultConfig, partialConfig); - if (!textMeasurerCtx) { - return nullShapeViewModel(cfg, { x: width / 2, y: height / 2 }); - } return shapeViewModel(spec, cfg); } diff --git a/src/index.ts b/src/index.ts index 22f1313754..91ac04aeb9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -49,6 +49,7 @@ export { export { Layer as PartitionLayer } from './chart_types/partition_chart/specs/index'; export * from './chart_types/goal_chart/specs/index'; export * from './chart_types/wordcloud/specs/index'; + export { Accessor, AccessorFn, diff --git a/src/specs/settings.tsx b/src/specs/settings.tsx index 91a82bb2d5..22a0e6be45 100644 --- a/src/specs/settings.tsx +++ b/src/specs/settings.tsx @@ -23,6 +23,7 @@ import { CustomXDomain, GroupByAccessor, Spec } from '.'; import { Cell } from '../chart_types/heatmap/layout/types/viewmodel_types'; import { PrimitiveValue } from '../chart_types/partition_chart/layout/utils/group_by_rollup'; import { LegendStrategy } from '../chart_types/partition_chart/layout/utils/highlighted_geoms'; +import { WordModel } from '../chart_types/wordcloud/layout/types/viewmodel_types'; import { XYChartSeriesIdentifier } from '../chart_types/xy_chart/utils/series'; import { SeriesIdentifier } from '../common/series_id'; import { TooltipPortalSettings } from '../components'; @@ -96,6 +97,8 @@ export type XYChartElementEvent = [GeometryValue, XYChartSeriesIdentifier]; export type PartitionElementEvent = [Array, SeriesIdentifier]; /** @public */ export type HeatmapElementEvent = [Cell, SeriesIdentifier]; +/** @public */ +export type WordCloudElementEvent = [WordModel, SeriesIdentifier]; /** * An object that contains the scaled mouse position based on @@ -131,11 +134,11 @@ export type ProjectionClickListener = (values: ProjectedValues) => void; /** @public */ export type ElementClickListener = ( - elements: Array, + elements: Array, ) => void; /** @public */ export type ElementOverListener = ( - elements: Array, + elements: Array, ) => void; /** @public */ export type BrushEndListener = (brushArea: XYBrushArea) => void; diff --git a/stories/wordcloud/1_wordcloud.tsx b/stories/wordcloud/1_wordcloud.tsx index 0fda70ea93..8d642e86fd 100644 --- a/stories/wordcloud/1_wordcloud.tsx +++ b/stories/wordcloud/1_wordcloud.tsx @@ -17,6 +17,7 @@ * under the License. */ +import { action } from '@storybook/addon-actions'; import { color, number, select } from '@storybook/addon-knobs'; import React from 'react'; @@ -278,12 +279,15 @@ export const Example = () => { return ( - {/* eslint-disable-next-line no-console */} { - // eslint-disable-next-line no-console - console.log('onElementClick', d); + const datum = d[0][0] as WordModel; + action('onElementClick')(`${datum.text}: ${datum.weight}`); + }} + onElementOver={(d) => { + const datum = d[0][0] as WordModel; + action('onElementOver')(`${datum.text}: ${datum.weight}`); }} /> { data={sampleData(text, palette as keyof typeof palettes)} weightFn={weightFn} outOfRoomCallback={(wordCount: number, renderedWordCount: number, renderedWords: string[]) => { - // eslint-disable-next-line no-console - console.log( + action('outOfRoomCallback')( `Managed to render ${renderedWordCount} words out of ${wordCount} words: ${renderedWords.join(', ')}`, ); }}