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 LegendWidget #91

Merged
merged 37 commits into from
Jun 24, 2021
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
bbf2715
Added Layer, Multi Layer and Header
aaranadev Feb 8, 2021
3ff13e1
added hover and and tooltip
aaranadev Feb 8, 2021
544c2b4
added categories
aaranadev Feb 9, 2021
bb6e088
added icon
aaranadev Feb 9, 2021
61b0ce6
added note
aaranadev Feb 9, 2021
c6fab38
added changeVisibility event
aaranadev Feb 9, 2021
9f57eb5
added tooltip
aaranadev Feb 9, 2021
4230abc
added proportion
aaranadev Feb 9, 2021
9850c85
added custom
aaranadev Feb 9, 2021
352d14e
Merge branch 'master' of github.com:CartoDB/carto-react-lib into feat…
aaranadev Feb 24, 2021
13e56b5
Removed empty bodies and improve ternary operators readability.
Clebal Mar 1, 2021
c3ae6b4
Allow multiple colors gradient in StepsContinuous
Clebal Mar 1, 2021
2ed82ae
Changed point to line to show avg in Proportion legend
menusal Mar 3, 2021
7d5719d
wip
menusal Mar 5, 2021
6a2c8f0
wip
menusal Mar 5, 2021
ad0c30c
wip
menusal Mar 5, 2021
3cc3a27
wip
menusal Mar 5, 2021
bc70b66
Revert some changes
menusal Mar 8, 2021
1c2c184
Update with dev
Clebal Apr 28, 2021
df4051c
Update every legend interface. Add LegendWidget.
Clebal May 4, 2021
def4387
Comment unused code
Clebal May 4, 2021
53a2e7c
Merge branch 'master' of github.com:CartoDB/carto-react into feature/…
Jun 23, 2021
477ff7f
Added new legend system to storybook
Jun 23, 2021
1888b0f
added types
Jun 23, 2021
79f5955
removed custom classname
Jun 23, 2021
697e56d
added test (wip)
Jun 24, 2021
dabb6e5
added testing
Jun 24, 2021
265bfcd
fixed test
Jun 24, 2021
c57c65b
improve legend widget
Jun 24, 2021
562804a
added classname
Jun 24, 2021
67755c2
changed legend by layer
Jun 24, 2021
d215c4d
improve css
Jun 24, 2021
88ef69c
remove legend without layers
Jun 24, 2021
633ecde
Add note to changelog
VictorVelarde Jun 24, 2021
cb9eea5
Add relative units in LegendIcon
VictorVelarde Jun 24, 2021
fe6f2a5
Small refactor extracting 2 functions
VictorVelarde Jun 24, 2021
38e990a
Unify import
VictorVelarde Jun 24, 2021
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
8 changes: 5 additions & 3 deletions packages/react-redux/__tests__/cartoSlice.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ describe('carto slice', () => {
describe('layer actions', () => {
const layerInfo = {
id: 'whatever-id',
source: 'whatever-source-id'
source: 'whatever-source-id',
visible: true
};

const extraInfo = {
Expand All @@ -84,10 +85,11 @@ describe('carto slice', () => {
test('should update a layer with extra layerAttributes info', () => {
store.dispatch(cartoSlice.updateLayer(extraInfo));
const { carto } = store.getState();
expect(carto.layers[layerInfo.id]).toEqual({
const expected = {
...layerInfo,
...extraInfo.layerAttributes
});
};
expect(carto.layers[layerInfo.id]).toEqual(expected);
});

test('should remove a layer', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-redux/src/slices/cartoSlice.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ export const removeSource = (sourceId) => ({
*/
export const addLayer = ({ id, source, layerAttributes = {} }) => ({
type: 'carto/addLayer',
payload: { id, source, ...layerAttributes }
payload: { id, source, visible: true, ...layerAttributes }
});

/**
Expand Down
115 changes: 115 additions & 0 deletions packages/react-ui/__tests__/widgets/LegendWidgetUI.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React from 'react';
import { getMaterialUIContext } from './testUtils';
import LegendWidgetUI from '../../src/widgets/legend/LegendWidgetUI';
import { render, screen } from '@testing-library/react';
import { Typography } from '@material-ui/core';

const CUSTOM_CHILDREN = <Typography>Legend custom</Typography>;

describe('LegendWidgetUI', () => {
const DATA = [
{
id: 'category',
title: 'Category Layer',
visible: true,
legend: {
type: 'category',
note: 'lorem',
colors: ['#000', '#00F', '#0F0'],
labels: ['Category 1', 'Category 2', 'Category 3']
}
},
{
id: 'icon',
title: 'Icon Layer',
visible: true,
legend: {
type: 'icon',
labels: ['Icon 1', 'Icon 2', 'Icon 3'],
icons: [
'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fmaxcdn.icons8.com%2FShare%2Ficon%2Fnolan%2FMaps%2Fmarker1600.png&f=1&nofb=1',
'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fmaxcdn.icons8.com%2FShare%2Ficon%2Fnolan%2FMaps%2Fmarker1600.png&f=1&nofb=1',
'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fmaxcdn.icons8.com%2FShare%2Ficon%2Fnolan%2FMaps%2Fmarker1600.png&f=1&nofb=1'
]
}
},
{
id: 'bins',
title: 'Ramp Layer',
visible: true,
legend: {
type: 'bins',
colors: ['#000', '#00F', '#0F0', '#F00'],
labels: [100, 200, 300]
}
},
{
id: 'continuous',
title: 'Ramp Layer',
visible: true,
legend: {
type: 'continuous_ramp',
colors: ['#000', '#00F', '#0F0', '#F00'],
labels: [100, 200, 300]
}
},
{
id: 'proportion',
title: 'Proportion Layer',
visible: true,
legend: {
type: 'proportion',
labels: [100, 500]
}
},
{
id: 'custom',
title: 'Single Layer',
visible: true,
legend: {
children: CUSTOM_CHILDREN
}
}
];
const Widget = (props) => getMaterialUIContext(<LegendWidgetUI {...props} />);

test('single legend', () => {
render(<Widget layers={[DATA[0]]}></Widget>);
expect(screen.queryByText('Layers')).not.toBeInTheDocument();
});

test('multiple legends', () => {
render(<Widget layers={DATA}></Widget>);
expect(screen.queryByText('Layers')).toBeInTheDocument();
});

test('Category legend', () => {
render(<Widget layers={[DATA[0]]}></Widget>);
expect(screen.getByTestId('categories-legend')).toBeInTheDocument();
});

test('Icon legend', () => {
render(<Widget layers={[DATA[1]]}></Widget>);
expect(screen.getByTestId('icon-legend')).toBeInTheDocument();
});

test('Bins legend', () => {
render(<Widget layers={[DATA[2]]}></Widget>);
expect(screen.getByTestId('ramp-legend')).toBeInTheDocument();
});

test('Continuous legend', () => {
render(<Widget layers={[DATA[3]]}></Widget>);
expect(screen.getByTestId('ramp-legend')).toBeInTheDocument();
});

test('Proportion legend', () => {
render(<Widget layers={[DATA[4]]}></Widget>);
expect(screen.getByTestId('proportion-legend')).toBeInTheDocument();
});

test('Custom legend', () => {
render(<Widget layers={[DATA[5]]}></Widget>);
expect(screen.getByText('Legend custom')).toBeInTheDocument();
});
});
3 changes: 2 additions & 1 deletion packages/react-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@
"jest": "^26.6.3",
"react-redux": "^7.2.2",
"webpack": "^5.24.2",
"webpack-cli": "^4.5.0"
"webpack-cli": "^4.5.0",
"cartocolor": "^4.0.2"
},
"dependencies": {
"@babel/runtime": "^7.13.9"
Expand Down
5 changes: 4 additions & 1 deletion packages/react-ui/src/index.d.ts
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 LegendWidgetUI, { LEGEND_TYPES } from './widgets/legend/LegendWidgetUI';
import ScatterPlotWidgetUI from './widgets/ScatterPlotWidgetUI';

export {
Expand All @@ -13,5 +14,7 @@ export {
FormulaWidgetUI,
HistogramWidgetUI,
PieWidgetUI,
ScatterPlotWidgetUI
ScatterPlotWidgetUI,
LegendWidgetUI,
LEGEND_TYPES,
};
5 changes: 4 additions & 1 deletion 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 LegendWidgetUI, { LEGEND_TYPES } from './widgets/legend/LegendWidgetUI';
import ScatterPlotWidgetUI from './widgets/ScatterPlotWidgetUI';

export {
Expand All @@ -13,5 +14,7 @@ export {
FormulaWidgetUI,
HistogramWidgetUI,
PieWidgetUI,
ScatterPlotWidgetUI
ScatterPlotWidgetUI,
LegendWidgetUI,
LEGEND_TYPES
};
125 changes: 78 additions & 47 deletions packages/react-ui/src/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,92 @@
export type WrapperWidgetUI = {
title: string,
isLoading?: boolean,
expandable?: boolean,
actions?: { id: string, name: string, icon: React.ReactElement, action: Function }[],
options?: { id: string, name: string, action: Function }[],
children?: React.ReactNode
title: string;
isLoading?: boolean;
expandable?: boolean;
actions?: { id: string; name: string; icon: React.ReactElement; action: Function }[];
options?: { id: string; name: string; action: Function }[];
children?: React.ReactNode;
};

export type CategoryWidgetUIData = { name: string, value: number }[]
export type CategoryWidgetUIData = { name: string; value: number }[];
export type CategoryWidgetUI = {
data: CategoryWidgetUIData,
isLoading?: boolean,
formatter?: Function,
labels?: object,
maxItems?: number,
selectedCategories?: string[],
onSelectedCategoriesChange?: Function,
order?: 'ranking' | 'fixed'
data: CategoryWidgetUIData;
isLoading?: boolean;
formatter?: Function;
labels?: object;
maxItems?: number;
selectedCategories?: string[];
onSelectedCategoriesChange?: Function;
order?: 'ranking' | 'fixed';
};

export type FormulaWidgetUIData = string | number | { value: string[] | number[], unit: string };
export type FormulaWidgetUIData =
| string
| number
| { value: string[] | number[]; unit: string };
export type FormulaWidgetUI = {
data: FormulaWidgetUIData,
unitBefore?: boolean,
formatter?: Function
}
data: FormulaWidgetUIData;
unitBefore?: boolean;
formatter?: Function;
};

export type HistogramWidgetUIData = number[];
export type HistogramWidgetUI = {
data: HistogramWidgetUIData,
tooltip?: boolean,
tooltipFormatter?: Function,
xAxisFormatter?: Function,
yAxisFormatter?: Function,
dataAxis?: unknown[],
name?: string,
onSelectedBarsChange?: Function,
height?: number
}

export type PieWidgetUIData = { name: string, value: number }[];
data: HistogramWidgetUIData;
tooltip?: boolean;
tooltipFormatter?: Function;
xAxisFormatter?: Function;
yAxisFormatter?: Function;
dataAxis?: unknown[];
name?: string;
onSelectedBarsChange?: Function;
height?: number;
};

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

export type Layer = {
id: string | number;
title: string;
switchable: boolean;
visible: boolean;
legend?: LegendWidgetUIData;
};

export type LegendWidgetUIData = {
type: string;
children?: Node,
collapsible?: boolean;
note?: string;
attr?: string;
colors?: string[];
labels?: (string | number)[];
icons?: string[];
stats?: {
min: number | string;
max: number | string;
};
};

export type LegendWidgetUI = {
layers: LegendWidgetUIData[];
onChangeVisibility: ({ id: string, visible: boolean }) => void;
};

export type ScatterPlotWidgetUIData = number[][];
export type ScatterPlotWidgetUI = {
name: string,
data: ScatterPlotWidgetUIData,
xAxisFormatter?: Function,
yAxisFormatter?: Function,
tooltipFormatter?: Function
}
name: string;
data: ScatterPlotWidgetUIData;
xAxisFormatter?: Function;
yAxisFormatter?: Function;
tooltipFormatter?: Function;
};
53 changes: 53 additions & 0 deletions packages/react-ui/src/utils/palette.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as cartoColors from 'cartocolor';

export const DEFAULT_PALETTE = 'PurpOr';
export const NULL_COLOR = [204, 204, 204];
export const OTHERS_COLOR = [119, 119, 119];

export function getPaletteFromCartoColors (name, domainSize) {
const palette = cartoColors[name];
let paletteIndex = domainSize;

if (!palette) {
throw new Error (`Palette "${name}" not found. Expected a CARTOColors string`)
}

const palettesColorVariants = Object.keys(palette)
.filter(p => p !== 'tags')
.map(Number);

const longestPaletteIndex = Math.max(...palettesColorVariants);
const smallestPaletteIndex = Math.min(...palettesColorVariants);

if (!Number.isInteger(domainSize) || domainSize > longestPaletteIndex) {
paletteIndex = longestPaletteIndex;
} else if (domainSize < smallestPaletteIndex) {
paletteIndex = smallestPaletteIndex;
}

let colors = palette[paletteIndex];

if (palette.tags && palette.tags.includes('qualitative')) {
colors = colors.slice(0, -1);
}

return colors;
}

export function getPalette(colors, domainSize) {
if (typeof colors === 'string') {
return getPaletteFromCartoColors(colors, domainSize.length)
} else {
return colors.map((c) => {
if (Array.isArray(c)) {
return rgbToHex(...c);
} else {
return c;
}
});
}
}

export function rgbToHex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
Loading