Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Scatter plot widget & deck.gl update to alpha 10 #149

Merged
merged 24 commits into from
Jun 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

- Add support to CARTO Cloud Native [#142](https://github.com/CartoDB/carto-react/pull/142)
- Update TypeScript typings to Cloud Native and remove SourceTypes from react-api public api[#147](https://github.com/CartoDB/carto-react/pull/147)
- Remove SourceTypes from react-api public api [#147](https://github.com/CartoDB/carto-react/pull/147)
- Add new ScatterplotWidget component [#149](https://github.com/CartoDB/carto-react/pull/149)
- Update to latest 8.5.0-alpha.10 deck.gl version [#149](https://github.com/CartoDB/carto-react/pull/149)
- Add support to Cloud Native SQL API [#150](https://github.com/CartoDB/carto-react/pull/150)


## 1.0.1 (2021-04-12)

- Add basic Typescript typings [#136](https://github.com/CartoDB/carto-react/pull/136)
Expand Down
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
"packages/*"
],
"devDependencies": {
"@deck.gl/carto": "8.5.0-alpha.9",
"@deck.gl/core": "8.5.0-alpha.9",
"@deck.gl/extensions": "8.5.0-alpha.9",
"@deck.gl/geo-layers": "8.5.0-alpha.9",
"@deck.gl/google-maps": "8.5.0-alpha.9",
"@deck.gl/layers": "8.5.0-alpha.9",
"@deck.gl/mesh-layers": "8.5.0-alpha.9",
"@deck.gl/carto": "8.5.0-alpha.10",
"@deck.gl/core": "8.5.0-alpha.10",
"@deck.gl/extensions": "8.5.0-alpha.10",
"@deck.gl/geo-layers": "8.5.0-alpha.10",
"@deck.gl/google-maps": "8.5.0-alpha.10",
"@deck.gl/layers": "8.5.0-alpha.10",
"@deck.gl/mesh-layers": "8.5.0-alpha.10",
"@material-ui/core": "^4.11.3",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.57",
Expand Down
6 changes: 3 additions & 3 deletions packages/react-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@
"@carto/react-core": "^1.1.0",
"@carto/react-redux": "^1.1.0",
"@carto/react-workers": "^1.1.0",
"@deck.gl/carto": "8.5.0-alpha.9",
"@deck.gl/core": "8.5.0-alpha.9",
"@deck.gl/extensions": "8.5.0-alpha.9",
"@deck.gl/carto": "8.5.0-alpha.10",
"@deck.gl/core": "8.5.0-alpha.10",
"@deck.gl/extensions": "8.5.0-alpha.10",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-redux": "^7.2.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/react-api/src/hooks/useCartoLayerProps.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DataFilterExtension } from '@deck.gl/extensions';
import { debounce, _buildFeatureFilter } from '@carto/react-core';
import { _buildFeatureFilter } from '@carto/react-core';
import useViewportFeatures from './useViewportFeatures';
import { MAP_TYPES, API_VERSIONS } from '@deck.gl/carto';

Expand Down
2 changes: 1 addition & 1 deletion packages/react-basemaps/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
},
"peerDependencies": {
"@carto/react-core": "^1.1.0",
"@deck.gl/google-maps": "8.5.0-alpha.9",
"@deck.gl/google-maps": "8.5.0-alpha.10",
"react": "^17.0.1",
"react-dom": "^17.0.1"
}
Expand Down
18 changes: 18 additions & 0 deletions packages/react-core/__tests__/operations/scatterPlot.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { scatterPlot } from '../../src/operations/scatterPlot';
test('filter invalid values', () => {
const data = [
{ x: 0 }, // Missing y
{ y: 1 }, // Missing x
{ x: null, y: 1 }, // null x
{ x: 1, y: null }, // null y
{ x: 0, y: 0 }, // zero for both
{ x: 1, y: 2 }, // valid
{}, // no values for both
{ x: 2, y: 3 } // valid
];
expect(scatterPlot(data, 'x', 'y')).toEqual([
[0, 0],
[1, 2],
[2, 3]
]);
});
1 change: 1 addition & 0 deletions packages/react-core/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export { AggregationTypes } from './operations/aggregation/AggregationTypes';
export { aggregationFunctions } from './operations/aggregation/values';
export { groupValuesByColumn } from './operations/groupby';
export { histogram } from './operations/histogram';
export { scatterPlot } from './operations/scatterPlot';

export {
FilterTypes as _FilterTypes,
Expand Down
13 changes: 13 additions & 0 deletions packages/react-core/src/operations/scatterPlot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Filters invalid features and formats data
*/
export const scatterPlot = (features, xAxisColumn, yAxisColumn) =>
features
.filter((feature) => {
const xValue = feature[xAxisColumn];
const xIsValid = xValue !== null && xValue !== undefined;
const yValue = feature[yAxisColumn];
const yIsValid = yValue !== null && yValue !== undefined;
return xIsValid && yIsValid;
})
.map((feature) => [feature[xAxisColumn], feature[yAxisColumn]]);
4 changes: 2 additions & 2 deletions packages/react-redux/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@
"peerDependencies": {
"@carto/react-core": "^1.1.0",
"@carto/react-workers": "^1.1.0",
"@deck.gl/carto": "8.5.0-alpha.9",
"@deck.gl/core": "8.5.0-alpha.9",
"@deck.gl/carto": "8.5.0-alpha.10",
"@deck.gl/core": "8.5.0-alpha.10",
"@reduxjs/toolkit": "^1.5.0"
}
}
34 changes: 34 additions & 0 deletions packages/react-ui/__tests__/widgets/ScatterPlotWidget.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import { render } from '@testing-library/react';
import ScatterPlotWidgetUI from '../../src/widgets/ScatterPlotWidgetUI';
import { getMaterialUIContext, mockEcharts } from './testUtils';

describe('ScatterPlotWidgetUI', () => {
beforeAll(() => {
mockEcharts.init();
});

afterAll(() => {
mockEcharts.destroy();
});
const DATA = [
[1, 2],
[2, 4],
[3, 6]
];
const Widget = (props) =>
getMaterialUIContext(
<ScatterPlotWidgetUI data={DATA} onSelectedBarsChange={() => {}} {...props} />
);

test('renders correctly', () => {
render(<Widget />);
});

test('re-render with different data', () => {
const { rerender } = render(<Widget />);

rerender(<Widget />);
rerender(<Widget data={[...DATA, [4, 8]]} />);
});
});
4 changes: 3 additions & 1 deletion packages/react-ui/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import CategoryWidgetUI from './widgets/CategoryWidgetUI';
import FormulaWidgetUI from './widgets/FormulaWidgetUI';
import HistogramWidgetUI from './widgets/HistogramWidgetUI';
import PieWidgetUI from './widgets/PieWidgetUI';
import ScatterPlotWidgetUI from './widgets/ScatterPlotWidgetUI';

export {
cartoThemeOptions,
WrapperWidgetUI,
CategoryWidgetUI,
FormulaWidgetUI,
HistogramWidgetUI,
PieWidgetUI
PieWidgetUI,
ScatterPlotWidgetUI
};
2 changes: 2 additions & 0 deletions packages/react-ui/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import CategoryWidgetUI from './widgets/CategoryWidgetUI';
import FormulaWidgetUI from './widgets/FormulaWidgetUI';
import HistogramWidgetUI from './widgets/HistogramWidgetUI';
import PieWidgetUI from './widgets/PieWidgetUI';
import ScatterPlotWidgetUI from './widgets/ScatterPlotWidgetUI';

export {
cartoThemeOptions,
Expand All @@ -12,4 +13,5 @@ export {
FormulaWidgetUI,
HistogramWidgetUI,
PieWidgetUI,
ScatterPlotWidgetUI
};
17 changes: 11 additions & 6 deletions packages/react-ui/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export type WrapperWidgetUI = {
};

export type CategoryWidgetUIData = { name: string, value: number }[]

export type CategoryWidgetUI = {
data: CategoryWidgetUIData,
isLoading?: boolean,
Expand All @@ -21,15 +20,13 @@ export type CategoryWidgetUI = {
};

export type FormulaWidgetUIData = string | number | { value: string[] | number[], unit: string };

export type FormulaWidgetUI = {
data: FormulaWidgetUIData,
unitBefore?: boolean,
formatter?: Function
}

export type HistogramWidgetUIData = number[];

export type HistogramWidgetUI = {
data: HistogramWidgetUIData,
tooltip?: boolean,
Expand All @@ -43,14 +40,22 @@ export type HistogramWidgetUI = {
}

export type PieWidgetUIData = { name: string, value: number }[];

export type PieWidgetUI = {
name: string,
data: PieWidgetUIData,
colors?: string[],
formatter?: Function,
tooltipFormatter?: Function,
height?: string,
colors?: string[],
selectedCategories?: string[],
onSelectedCategoriesChange?: Function
}
}

export type ScatterPlotWidgetUIData = number[][];
export type ScatterPlotWidgetUI = {
name: string,
data: ScatterPlotWidgetUIData,
xAxisFormatter?: Function,
yAxisFormatter?: Function,
tooltipFormatter?: Function
}
109 changes: 109 additions & 0 deletions packages/react-ui/src/widgets/ScatterPlotWidgetUI.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { useTheme } from '@material-ui/core';
import PropTypes from 'prop-types';
import React, { useRef, useState, useEffect } from 'react';
import ReactEcharts from 'echarts-for-react';
import { isDataEqual } from './utils/chartUtils';
function __generateDefaultConfig(
{ tooltipFormatter, xAxisFormatter = (v) => v, yAxisFormatter = (v) => v },
theme
) {
return {
grid: {},
tooltip: {
padding: [theme.spacing(0.5), theme.spacing(1)],
textStyle: {
...theme.typography.caption,
fontSize: 12,
lineHeight: 16
},
backgroundColor: theme.palette.other.tooltip,
...(tooltipFormatter ? { formatter: tooltipFormatter } : {})
},
color: [theme.palette.secondary.main],
xAxis: {
axisLabel: {
...theme.typography.charts,
padding: [theme.spacing(0.5), 0, 0, 0],
formatter: (v) => {
const formatted = xAxisFormatter(v);
return typeof formatted === 'object'
? `${formatted.prefix || ''}${formatted.value}${formatted.suffix || ''}`
: formatted;
}
}
},
yAxis: {
axisLabel: {
...theme.typography.charts,
formatter: (v) => {
const formatted = yAxisFormatter(v);
return typeof formatted === 'object'
? `${formatted.prefix}${formatted.value}${formatted.suffix || ''}`
: formatted;
}
}
}
};
}

function __generateSerie({ name, data, theme }) {
return [
{
type: 'scatter',
name,
data: data
}
];
}

const EchartsWrapper = React.memo(
ReactEcharts,
({ option: optionPrev }, { option: optionNext }) => isDataEqual(optionPrev, optionNext)
);

function ScatterPlotWidgetUI({
name,
data = [],
xAxisFormatter,
yAxisFormatter,
tooltipFormatter
}) {
const theme = useTheme();
const chartInstance = useRef();
const [options, setOptions] = useState({
series: []
});

useEffect(() => {
const config = __generateDefaultConfig(
{ xAxisFormatter, yAxisFormatter, tooltipFormatter },
theme
);
const series = __generateSerie({
name,
data: data || []
});
setOptions({
...config,
series
});
}, [data, name, theme, xAxisFormatter, yAxisFormatter, tooltipFormatter]);
return <EchartsWrapper ref={chartInstance} option={options} lazyUpdate={true} />;
}

ScatterPlotWidgetUI.defaultProps = {
name: null,
tooltipFormatter: (v) => `[${v.value[0]}, ${v.value[1]})`,
xAxisFormatter: (v) => v,
yAxisFormatter: (v) => v
};

ScatterPlotWidgetUI.propTypes = {
name: PropTypes.string,
data: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)),
tooltipFormatter: PropTypes.func,
xAxisFormatter: PropTypes.func,
yAxisFormatter: PropTypes.func
};

export default ScatterPlotWidgetUI;
Loading