Skip to content

Commit 196fb6a

Browse files
authored
feat(wordcloud): click and over events on text (#1180)
The `<Worldcloud>` chart now fully support the `onElementClick`, `onElementOver` and `onElementOut` events, returning the original datum the callback. close #1156
1 parent a64f333 commit 196fb6a

12 files changed

Lines changed: 182 additions & 77 deletions

File tree

api/charts.api.md

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -678,10 +678,10 @@ export type DisplayValueStyle = Omit<TextStyle, 'fill' | 'fontSize'> & {
678678
export type DomainRange = LowerBoundedDomain | UpperBoundedDomain | CompleteBoundedDomain | UnboundedDomainWithInterval;
679679

680680
// @public (undocumented)
681-
export type ElementClickListener = (elements: Array<XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent>) => void;
681+
export type ElementClickListener = (elements: Array<XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent | WordCloudElementEvent>) => void;
682682

683683
// @public (undocumented)
684-
export type ElementOverListener = (elements: Array<XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent>) => void;
684+
export type ElementOverListener = (elements: Array<XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent | WordCloudElementEvent>) => void;
685685

686686
// @public (undocumented)
687687
export const entryKey: ([key]: ArrayEntry) => string;
@@ -1330,6 +1330,9 @@ export interface OrderBy {
13301330
// @public (undocumented)
13311331
export type OrdinalDomain = (number | string)[];
13321332

1333+
// @public (undocumented)
1334+
export type OutOfRoomCallback = (wordCount: number, renderedWordCount: number, renderedWords: string[]) => void;
1335+
13331336
// Warning: (ae-forgotten-export) The symbol "PerSideDistance" needs to be exported by the entry point index.d.ts
13341337
//
13351338
// @public (undocumented)
@@ -2114,22 +2117,66 @@ export interface Visible {
21142117
visible: boolean;
21152118
}
21162119

2120+
// @public (undocumented)
2121+
export const WeightFn: Readonly<{
2122+
log: "log";
2123+
linear: "linear";
2124+
exponential: "exponential";
2125+
squareRoot: "squareRoot";
2126+
}>;
2127+
2128+
// @public (undocumented)
2129+
export type WeightFn = $Values<typeof WeightFn>;
2130+
21172131
// Warning: (ae-forgotten-export) The symbol "SpecRequiredProps" needs to be exported by the entry point index.d.ts
21182132
// Warning: (ae-forgotten-export) The symbol "SpecOptionalProps" needs to be exported by the entry point index.d.ts
21192133
//
21202134
// @alpha (undocumented)
21212135
export const Wordcloud: React_2.FunctionComponent<SpecRequiredProps_9 & SpecOptionalProps_9>;
21222136

2137+
// @public (undocumented)
2138+
export interface WordcloudConfigs {
2139+
// (undocumented)
2140+
count: number;
2141+
// (undocumented)
2142+
endAngle: number;
2143+
// (undocumented)
2144+
exponent: number;
2145+
// (undocumented)
2146+
fontFamily: string;
2147+
// (undocumented)
2148+
fontStyle: string;
2149+
// (undocumented)
2150+
fontWeight: number;
2151+
// (undocumented)
2152+
height: number;
2153+
// (undocumented)
2154+
maxFontSize: number;
2155+
// (undocumented)
2156+
minFontSize: number;
2157+
// (undocumented)
2158+
padding: number;
2159+
// (undocumented)
2160+
spiral: string;
2161+
// (undocumented)
2162+
startAngle: number;
2163+
// (undocumented)
2164+
weightFn: WeightFn;
2165+
// (undocumented)
2166+
width: number;
2167+
}
2168+
2169+
// @public (undocumented)
2170+
export type WordCloudElementEvent = [WordModel, SeriesIdentifier];
2171+
21232172
// @alpha (undocumented)
21242173
export interface WordcloudSpec extends Spec {
21252174
// (undocumented)
21262175
angleCount: number;
21272176
// (undocumented)
21282177
chartType: typeof ChartType.Wordcloud;
21292178
// (undocumented)
2130-
config: RecursivePartial<PartitionConfig>;
2131-
// Warning: (ae-forgotten-export) The symbol "WordModel" needs to be exported by the entry point index.d.ts
2132-
//
2179+
config: RecursivePartial<WordcloudConfigs>;
21332180
// (undocumented)
21342181
data: WordModel[];
21352182
// (undocumented)
@@ -2146,8 +2193,6 @@ export interface WordcloudSpec extends Spec {
21462193
maxFontSize: number;
21472194
// (undocumented)
21482195
minFontSize: number;
2149-
// Warning: (ae-forgotten-export) The symbol "OutOfRoomCallback" needs to be exported by the entry point index.d.ts
2150-
//
21512196
// (undocumented)
21522197
outOfRoomCallback: OutOfRoomCallback;
21532198
// (undocumented)
@@ -2158,12 +2203,20 @@ export interface WordcloudSpec extends Spec {
21582203
spiral: string;
21592204
// (undocumented)
21602205
startAngle: number;
2161-
// Warning: (ae-forgotten-export) The symbol "WeightFn" needs to be exported by the entry point index.d.ts
2162-
//
21632206
// (undocumented)
21642207
weightFn: WeightFn;
21652208
}
21662209

2210+
// @public (undocumented)
2211+
export interface WordModel {
2212+
// (undocumented)
2213+
color: Color;
2214+
// (undocumented)
2215+
text: string;
2216+
// (undocumented)
2217+
weight: number;
2218+
}
2219+
21672220
// @public (undocumented)
21682221
export type XScaleType = typeof ScaleType.Ordinal | ScaleContinuousType;
21692222

src/chart_types/partition_chart/state/selectors/get_debug_state.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
HeatmapElementEvent,
2626
LayerValue,
2727
PartitionElementEvent,
28+
WordCloudElementEvent,
2829
XYChartElementEvent,
2930
} from '../../../../specs/settings';
3031
import { onMouseDown, onMouseUp, onPointerMove } from '../../../../state/actions/mouse';
@@ -68,7 +69,7 @@ describe.each([
6869
let store: Store<GlobalChartState>;
6970
let onClickListener: jest.Mock<
7071
undefined,
71-
Array<(XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent)[]>
72+
Array<(XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent | WordCloudElementEvent)[]>
7273
>;
7374
let debugState: DebugState;
7475

@@ -113,7 +114,10 @@ describe.each([
113114

114115
function expectCorrectClickInfo(
115116
store: Store<GlobalChartState>,
116-
onClickListener: jest.Mock<undefined, Array<(XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent)[]>>,
117+
onClickListener: jest.Mock<
118+
undefined,
119+
Array<(XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent | WordCloudElementEvent)[]>
120+
>,
117121
partition: SinglePartitionDebugState,
118122
index: number,
119123
) {

src/chart_types/partition_chart/state/selectors/picked_shapes.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
HeatmapElementEvent,
2929
GroupBySpec,
3030
SmallMultiplesSpec,
31+
WordCloudElementEvent,
3132
} from '../../../../specs';
3233
import { updateParentDimensions } from '../../../../state/actions/chart_settings';
3334
import { onMouseDown, onMouseUp, onPointerMove } from '../../../../state/actions/mouse';
@@ -101,7 +102,7 @@ describe('Picked shapes selector', () => {
101102
test('treemap check picked geometries', () => {
102103
const onClickListener = jest.fn<
103104
undefined,
104-
Array<(XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent)[]>
105+
Array<XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent | WordCloudElementEvent>[]
105106
>((): undefined => undefined);
106107
addSeries(store, treemapSpec, {
107108
onElementClick: onClickListener,
@@ -154,7 +155,7 @@ describe('Picked shapes selector', () => {
154155
test('small multiples pie chart check picked geometries', () => {
155156
const onClickListener = jest.fn<
156157
undefined,
157-
Array<(XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent)[]>
158+
Array<XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent | WordCloudElementEvent>[]
158159
>((): undefined => undefined);
159160
addSmallMultiplesSeries(
160161
store,
@@ -222,7 +223,7 @@ describe('Picked shapes selector', () => {
222223
test('sunburst check picked geometries', () => {
223224
const onClickListener = jest.fn<
224225
undefined,
225-
Array<(XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent)[]>
226+
Array<XYChartElementEvent | PartitionElementEvent | HeatmapElementEvent | WordCloudElementEvent>[]
226227
>((): undefined => undefined);
227228
addSeries(store, sunburstSpec, {
228229
onElementClick: onClickListener,

src/chart_types/wordcloud/layout/types/viewmodel_types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@ export interface Word {
6464
y0: number;
6565
y1: number;
6666
yoff: number;
67+
datum: WordModel;
6768
}
6869

69-
/** @internal */
70+
/** @public */
7071
export interface Configs {
7172
count: number;
7273
endAngle: number;
@@ -122,6 +123,7 @@ export type ShapeViewModel = {
122123
wordcloudViewModel: WordcloudViewModel;
123124
chartCenter: PointObject;
124125
pickQuads: PickFunction;
126+
specId: string;
125127
};
126128

127129
const commonDefaults: WordcloudViewModel = {
@@ -158,4 +160,5 @@ export const nullShapeViewModel = (specifiedConfig?: Config, chartCenter?: Point
158160
wordcloudViewModel: nullWordcloudViewModel,
159161
chartCenter: chartCenter || { x: 0, y: 0 },
160162
pickQuads: () => [],
163+
specId: 'empty',
161164
});

src/chart_types/wordcloud/layout/viewmodel/viewmodel.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export function shapeViewModel(spec: WordcloudSpec, config: Config): ShapeViewMo
3434
};
3535

3636
const {
37+
id,
3738
startAngle,
3839
endAngle,
3940
angleCount,
@@ -78,5 +79,6 @@ export function shapeViewModel(spec: WordcloudSpec, config: Config): ShapeViewMo
7879
chartCenter,
7980
wordcloudViewModel,
8081
pickQuads,
82+
specId: id,
8183
};
8284
}

src/chart_types/wordcloud/renderer/svg/connected_component.tsx

Lines changed: 78 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { connect } from 'react-redux';
2424
import { bindActionCreators, Dispatch } from 'redux';
2525

2626
import { ScreenReaderSummary } from '../../../../components/accessibility';
27+
import { SettingsSpec, WordCloudElementEvent } from '../../../../specs/settings';
2728
import { onChartRendered } from '../../../../state/actions/chart';
2829
import { GlobalChartState } from '../../../../state/chart_state';
2930
import {
@@ -32,6 +33,7 @@ import {
3233
getA11ySettingsSelector,
3334
} from '../../../../state/selectors/get_accessibility_config';
3435
import { getInternalIsInitializedSelector, InitStatus } from '../../../../state/selectors/get_internal_is_intialized';
36+
import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs';
3537
import { Dimensions } from '../../../../utils/dimensions';
3638
import { Configs, Datum, nullShapeViewModel, ShapeViewModel, Word } from '../../layout/types/viewmodel_types';
3739
import { geometries } from '../../state/selectors/geometries';
@@ -104,6 +106,7 @@ function layoutMaker(config: Configs, data: Datum[]) {
104106
const words = data.map((d) => {
105107
const weightFn = weightFnLookup[config.weightFn];
106108
return {
109+
datum: d,
107110
text: d.text,
108111
color: d.color,
109112
fontFamily: config.fontFamily,
@@ -125,36 +128,74 @@ function layoutMaker(config: Configs, data: Datum[]) {
125128
.fontSize((d: Word) => getFontSize(d));
126129
}
127130

128-
const View = ({ words, conf }: { words: Word[]; conf: Configs }) => (
129-
<svg width={getWidth(conf)} height={getHeight(conf)} role="presentation">
130-
<g transform={`translate(${getWidth(conf) / 2}, ${getHeight(conf) / 2})`}>
131-
{words.map((d, i) => {
132-
return (
133-
<text
134-
key={String(i)}
135-
style={{
136-
fontSize: getFontSize(d),
137-
fontStyle: getFontStyle(d),
138-
fontFamily: getFont(d),
139-
fontWeight: getFontWeight(d),
140-
fill: d.color,
141-
}}
142-
textAnchor="middle"
143-
transform={`translate(${d.x}, ${d.y}) rotate(${d.rotate})`}
144-
>
145-
{d.text}
146-
</text>
147-
);
148-
})}
149-
</g>
150-
</svg>
151-
);
131+
const View = ({
132+
words,
133+
conf,
134+
actions: { onElementClick, onElementOver, onElementOut },
135+
specId,
136+
}: {
137+
words: Word[];
138+
conf: Configs;
139+
actions: {
140+
onElementClick?: SettingsSpec['onElementClick'];
141+
onElementOver?: SettingsSpec['onElementOver'];
142+
onElementOut?: SettingsSpec['onElementOut'];
143+
};
144+
specId: string;
145+
}) => {
146+
return (
147+
<svg width={getWidth(conf)} height={getHeight(conf)} role="presentation">
148+
<g transform={`translate(${getWidth(conf) / 2}, ${getHeight(conf) / 2})`}>
149+
{words.map((d, i) => {
150+
const elements: WordCloudElementEvent[] = [[d.datum, { specId, key: specId }]];
151+
const actions = {
152+
...(onElementClick && {
153+
onClick: () => {
154+
onElementClick(elements);
155+
},
156+
}),
157+
...(onElementOver && {
158+
onMouseOver: () => {
159+
onElementOver(elements);
160+
},
161+
}),
162+
...(onElementOut && {
163+
onMouseOut: () => {
164+
onElementOut();
165+
},
166+
}),
167+
};
168+
return (
169+
<text
170+
key={String(i)}
171+
style={{
172+
fontSize: getFontSize(d),
173+
fontStyle: getFontStyle(d),
174+
fontFamily: getFont(d),
175+
fontWeight: getFontWeight(d),
176+
fill: d.color,
177+
}}
178+
textAnchor="middle"
179+
transform={`translate(${d.x}, ${d.y}) rotate(${d.rotate})`}
180+
{...actions}
181+
>
182+
{d.text}
183+
</text>
184+
);
185+
})}
186+
</g>
187+
</svg>
188+
);
189+
};
152190

153191
interface ReactiveChartStateProps {
154192
initialized: boolean;
155193
geometries: ShapeViewModel;
156194
chartContainerDimensions: Dimensions;
157195
a11ySettings: A11ySettings;
196+
onElementClick?: SettingsSpec['onElementClick'];
197+
onElementOver?: SettingsSpec['onElementOver'];
198+
onElementOut?: SettingsSpec['onElementOut'];
158199
}
159200

160201
interface ReactiveChartDispatchProps {
@@ -182,8 +223,11 @@ class Component extends React.Component<Props> {
182223
const {
183224
initialized,
184225
chartContainerDimensions: { width, height },
185-
geometries: { wordcloudViewModel },
226+
geometries: { wordcloudViewModel, specId },
186227
a11ySettings,
228+
onElementClick,
229+
onElementOver,
230+
onElementOut,
187231
} = this.props;
188232
if (!initialized || width === 0 || height === 0) {
189233
return null;
@@ -224,7 +268,12 @@ class Component extends React.Component<Props> {
224268

225269
return (
226270
<figure aria-labelledby={a11ySettings.labelId} aria-describedby={a11ySettings.descriptionId}>
227-
<View words={renderedWordObjects} conf={conf1} />
271+
<View
272+
words={renderedWordObjects}
273+
conf={conf1}
274+
actions={{ onElementClick, onElementOut, onElementOver }}
275+
specId={specId}
276+
/>
228277
<ScreenReaderSummary />
229278
</figure>
230279
);
@@ -260,6 +309,9 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => {
260309
geometries: geometries(state),
261310
chartContainerDimensions: state.parentDimensions,
262311
a11ySettings: getA11ySettingsSelector(state),
312+
onElementClick: getSettingsSpecSelector(state).onElementClick,
313+
onElementOver: getSettingsSpecSelector(state).onElementOver,
314+
onElementOut: getSettingsSpecSelector(state).onElementOut,
263315
};
264316
};
265317

0 commit comments

Comments
 (0)