Skip to content

Commit

Permalink
Migrate Word Cloud visualization to React (#3930)
Browse files Browse the repository at this point in the history
  • Loading branch information
kravets-levko authored Jul 3, 2019
1 parent 300f3f6 commit cc48de0
Show file tree
Hide file tree
Showing 13 changed files with 509 additions and 201 deletions.
45 changes: 5 additions & 40 deletions client/app/directives/resize-event.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,13 @@
import { findIndex } from 'lodash';

const items = new Map();

function checkItems() {
items.forEach((item, node) => {
const bounds = node.getBoundingClientRect();
// convert to int (because these numbers needed for comparisons), but preserve 1 decimal point
const width = Math.round(bounds.width * 10);
const height = Math.round(bounds.height * 10);

if (
(item.width !== width) ||
(item.height !== height)
) {
item.width = width;
item.height = height;
item.callback(node);
}
});

setTimeout(checkItems, 100);
}

checkItems(); // ensure it was called only once!
import resizeObserver from '@/services/resizeObserver';

function resizeEvent() {
return {
restrict: 'A',
link($scope, $element, attrs) {
const node = $element[0];
if (!items.has(node)) {
items.set(node, {
callback: () => {
$scope.$evalAsync(attrs.resizeEvent);
},
});

$scope.$on('$destroy', () => {
const index = findIndex(items, item => item.node === node);
if (index >= 0) {
items.splice(index, 1); // remove item
}
});
}
const unwatch = resizeObserver($element[0], () => {
$scope.$evalAsync(attrs.resizeEvent);
});
$scope.$on('$destroy', unwatch);
},
};
}
Expand Down
6 changes: 3 additions & 3 deletions client/app/lib/hooks/useQueryResult.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { useState, useEffect } from 'react';

function getQueryResultData(queryResult) {
return {
columns: queryResult ? queryResult.getColumns() : [],
rows: queryResult ? queryResult.getData() : [],
filters: queryResult ? queryResult.getFilters() : [],
columns: (queryResult && queryResult.getColumns()) || [],
rows: (queryResult && queryResult.getData()) || [],
filters: (queryResult && queryResult.getFilters()) || [],
};
}

Expand Down
1 change: 1 addition & 0 deletions client/app/pages/dashboards/dashboard.less
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
.sunburst-visualization-container,
.sankey-visualization-container,
.map-visualization-container,
.word-cloud-visualization-container,
.plotly-chart-container {
position: absolute;
left: 0;
Expand Down
50 changes: 50 additions & 0 deletions client/app/services/resizeObserver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* global ResizeObserver */

function observeNative(node, callback) {
if ((typeof ResizeObserver === 'function') && node) {
const observer = new ResizeObserver(() => callback()); // eslint-disable-line compat/compat
observer.observe(node);
return () => observer.disconnect();
}
return null;
}

const items = new Map();

function checkItems() {
if (items.size > 0) {
items.forEach((item, node) => {
const bounds = node.getBoundingClientRect();
// convert to int (because these numbers needed for comparisons), but preserve 1 decimal point
const width = Math.round(bounds.width * 10);
const height = Math.round(bounds.height * 10);

if (
(item.width !== width) ||
(item.height !== height)
) {
item.width = width;
item.height = height;
item.callback(node);
}
});

setTimeout(checkItems, 100);
}
}

function observeFallback(node, callback) {
if (node && !items.has(node)) {
const shouldTrigger = items.size === 0;
items.set(node, { callback });
if (shouldTrigger) {
checkItems();
}
return () => items.delete(node);
}
return null;
}

export default function observe(node, callback) {
return observeNative(node, callback) || observeFallback(node, callback) || (() => {});
}
4 changes: 2 additions & 2 deletions client/app/visualizations/EditVisualizationDialog.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { extend, map, findIndex, isEqual } from 'lodash';
import { extend, map, sortBy, findIndex, isEqual } from 'lodash';
import React, { useState, useMemo } from 'react';
import PropTypes from 'prop-types';
import Modal from 'antd/lib/modal';
Expand Down Expand Up @@ -158,7 +158,7 @@ function EditVisualizationDialog({ dialog, visualization, query, queryResult })
onChange={onTypeChanged}
>
{map(
registeredVisualizations,
sortBy(registeredVisualizations, ['type']),
vis => <Select.Option key={vis.type} data-test={'VisualizationType.' + vis.type}>{vis.name}</Select.Option>,
)}
</Select>
Expand Down
109 changes: 90 additions & 19 deletions client/app/visualizations/word-cloud/Editor.jsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,101 @@
import { map } from 'lodash';
import { map, merge } from 'lodash';
import React from 'react';
import Select from 'antd/lib/select';
import InputNumber from 'antd/lib/input-number';
import * as Grid from 'antd/lib/grid';
import { EditorPropTypes } from '@/visualizations';

const { Option } = Select;

export default function Editor({ options, data, onOptionsChange }) {
const onColumnChanged = (column) => {
const newOptions = { ...options, column };
onOptionsChange(newOptions);
const optionsChanged = (newOptions) => {
onOptionsChange(merge({}, options, newOptions));
};

return (
<div className="form-group">
<label className="control-label" htmlFor="word-cloud-column">Word Cloud Column Name</label>
<Select
id="word-cloud-column"
className="w-100"
value={options.column}
onChange={onColumnChanged}
>
{map(data.columns, ({ name }) => (
<Option key={name}>{name}</Option>
))}
</Select>
</div>
<React.Fragment>
<div className="form-group">
<label className="control-label" htmlFor="word-cloud-words-column">Words Column</label>
<Select
data-test="WordCloud.WordsColumn"
id="word-cloud-words-column"
className="w-100"
value={options.column}
onChange={column => optionsChanged({ column })}
>
{map(data.columns, ({ name }) => (
<Select.Option key={name} data-test={'WordCloud.WordsColumn.' + name}>{name}</Select.Option>
))}
</Select>
</div>
<div className="form-group">
<label className="control-label" htmlFor="word-cloud-frequencies-column">Frequencies Column</label>
<Select
data-test="WordCloud.FrequenciesColumn"
id="word-cloud-frequencies-column"
className="w-100"
value={options.frequenciesColumn}
onChange={frequenciesColumn => optionsChanged({ frequenciesColumn })}
>
<Select.Option key="none" value=""><i>(count word frequencies automatically)</i></Select.Option>
{map(data.columns, ({ name }) => (
<Select.Option key={'column-' + name} value={name} data-test={'WordCloud.FrequenciesColumn.' + name}>{name}</Select.Option>
))}
</Select>
</div>
<div className="form-group">
<label className="control-label" htmlFor="word-cloud-word-length-limit">
Words Length Limit
</label>
<Grid.Row gutter={15} type="flex">
<Grid.Col span={12}>
<InputNumber
data-test="WordCloud.WordLengthLimit.Min"
className="w-100"
placeholder="Min"
min={0}
value={options.wordLengthLimit.min}
onChange={value => optionsChanged({ wordLengthLimit: { min: value > 0 ? value : null } })}
/>
</Grid.Col>
<Grid.Col span={12}>
<InputNumber
data-test="WordCloud.WordLengthLimit.Max"
className="w-100"
placeholder="Max"
min={0}
value={options.wordLengthLimit.max}
onChange={value => optionsChanged({ wordLengthLimit: { max: value > 0 ? value : null } })}
/>
</Grid.Col>
</Grid.Row>
</div>
<div className="form-group">
<label className="control-label" htmlFor="word-cloud-word-length-limit">
Frequencies Limit
</label>
<Grid.Row gutter={15} type="flex">
<Grid.Col span={12}>
<InputNumber
data-test="WordCloud.WordCountLimit.Min"
className="w-100"
placeholder="Min"
min={0}
value={options.wordCountLimit.min}
onChange={value => optionsChanged({ wordCountLimit: { min: value > 0 ? value : null } })}
/>
</Grid.Col>
<Grid.Col span={12}>
<InputNumber
data-test="WordCloud.WordCountLimit.Max"
className="w-100"
placeholder="Max"
min={0}
value={options.wordCountLimit.max}
onChange={value => optionsChanged({ wordCountLimit: { max: value > 0 ? value : null } })}
/>
</Grid.Col>
</Grid.Row>
</div>
</React.Fragment>
);
}

Expand Down
Loading

0 comments on commit cc48de0

Please sign in to comment.