From ec04b93fb61eeef5e267aadfe258db352a72ad39 Mon Sep 17 00:00:00 2001 From: Mustafa Saifee Date: Mon, 25 Mar 2024 22:57:56 +0200 Subject: [PATCH] add log scale for scatter plot --- src/Components/AggregatedDataExplorer.tsx | 21 ++++++ src/Components/CountryVisualization.tsx | 18 +++++ src/Context/Context.tsx | 4 ++ src/Context/Reducer.tsx | 4 ++ src/GrapherComponent/ScatterPlot/Graph.tsx | 71 +++++++++++++------ .../Settings/ScatterPlotSettings.tsx | 24 +++++++ src/RegionVisualization/Visualization.tsx | 18 +++++ src/Types.ts | 4 ++ 8 files changed, 142 insertions(+), 22 deletions(-) diff --git a/src/Components/AggregatedDataExplorer.tsx b/src/Components/AggregatedDataExplorer.tsx index ea9732a..07d88fe 100644 --- a/src/Components/AggregatedDataExplorer.tsx +++ b/src/Components/AggregatedDataExplorer.tsx @@ -60,6 +60,8 @@ function AggregatedDataExplorer(props: Props) { signatureSolution: undefined, signatureSolutionForDataList: 'All', keepAxisSame: false, + xScaleType: 'linear', + yScaleType: 'linear', }; const [state, dispatch] = useReducer(Reducer, initialState); @@ -202,18 +204,35 @@ function AggregatedDataExplorer(props: Props) { payload: useSameRange, }); }; + const updateBarLayout = (verticalBarLayout: boolean) => { dispatch({ type: 'UPDATE_BAR_LAYOUT', payload: verticalBarLayout, }); }; + const updateKeepAxisSame = (d: boolean) => { dispatch({ type: 'UPDATE_KEEP_AXIS_SAME', payload: d, }); }; + + const updateXScaleType = (d: 'linear' | 'log') => { + dispatch({ + type: 'UPDATE_X_SCALE_TYPE', + payload: d, + }); + }; + + const updateYScaleType = (d: 'linear' | 'log') => { + dispatch({ + type: 'UPDATE_Y_SCALE_TYPE', + payload: d, + }); + }; + return (
{ + dispatch({ + type: 'UPDATE_X_SCALE_TYPE', + payload: d, + }); + }; + + const updateYScaleType = (d: 'linear' | 'log') => { + dispatch({ + type: 'UPDATE_Y_SCALE_TYPE', + payload: d, + }); + }; return (
diff --git a/src/Context/Context.tsx b/src/Context/Context.tsx index 88ee047..fe56620 100644 --- a/src/Context/Context.tsx +++ b/src/Context/Context.tsx @@ -34,6 +34,8 @@ const Context = createContext({ signatureSolutionForDataList: 'All', showReference: false, keepAxisSame: false, + xScaleType: 'linear', + yScaleType: 'linear', updateGraphType: ( _d: | 'scatterPlot' @@ -76,6 +78,8 @@ const Context = createContext({ | 'Resilience', ) => {}, updateKeepAxisSame: (_d: boolean) => {}, + updateXScaleType: (_d: 'linear' | 'log') => {}, + updateYScaleType: (_d: 'linear' | 'log') => {}, }); export default Context; diff --git a/src/Context/Reducer.tsx b/src/Context/Reducer.tsx index 9ca1c66..52a99a7 100644 --- a/src/Context/Reducer.tsx +++ b/src/Context/Reducer.tsx @@ -48,6 +48,10 @@ export default (state: any, action: any) => { return { ...state, disaggregationOrder: action.payload }; case 'UPDATE_KEEP_AXIS_SAME': return { ...state, keepAxisSame: action.payload }; + case 'UPDATE_X_SCALE_TYPE': + return { ...state, xScaleType: action.payload }; + case 'UPDATE_Y_SCALE_TYPE': + return { ...state, yScaleType: action.payload }; default: return { ...state }; } diff --git a/src/GrapherComponent/ScatterPlot/Graph.tsx b/src/GrapherComponent/ScatterPlot/Graph.tsx index adfdc49..8d01822 100644 --- a/src/GrapherComponent/ScatterPlot/Graph.tsx +++ b/src/GrapherComponent/ScatterPlot/Graph.tsx @@ -4,7 +4,13 @@ import maxBy from 'lodash.maxby'; import max from 'lodash.max'; import orderBy from 'lodash.orderby'; import { Delaunay } from 'd3-delaunay'; -import { scaleOrdinal, scaleLinear, scaleThreshold, scaleSqrt } from 'd3-scale'; +import { + scaleOrdinal, + scaleLinear, + scaleThreshold, + scaleSqrt, + scaleLog, +} from 'd3-scale'; import minBy from 'lodash.minby'; import UNDPColorModule from 'undp-viz-colors'; import flattenDeep from 'lodash.flattendeep'; @@ -49,6 +55,8 @@ export function Graph(props: Props) { selectedIncomeGroups, selectedCountryGroup, keepAxisSame, + xScaleType, + yScaleType, } = useContext(Context) as CtxDataType; const [selectedColor, setSelectedColor] = useState( undefined, @@ -342,17 +350,28 @@ export function Graph(props: Props) { : refYVal : (minBy(dataFormatted, d => d.yVal)?.yVal as number) : 0; - - const xScale = scaleLinear() - .domain([xMinValue > 0 ? 0 : xMinValue, xMaxValue]) - .range([0, graphWidth]) - .nice(); - const yScale = scaleLinear() - .domain([yMinValue > 0 ? 0 : yMinValue, yMaxValue]) - .range([graphHeight, 0]) - .nice(); - const xTicks = xScale.ticks(5); - const yTicks = yScale.ticks(5); + const xScaleLogAllowed = !( + xScaleType === 'linear' || + fullArray.filter(d => (d.x as number) <= 0).length > 0 + ); + const yScaleLogAllowed = !( + yScaleType === 'linear' || + fullArray.filter(d => (d.y as number) <= 0).length > 0 + ); + const xScale = !xScaleLogAllowed + ? scaleLinear() + .domain([xMinValue > 0 ? 0 : xMinValue, xMaxValue]) + .range([0, graphWidth]) + .nice() + : scaleLog().domain([xMinValue, xMaxValue]).range([0, graphWidth]).nice(); + const yScale = !yScaleLogAllowed + ? scaleLinear() + .domain([yMinValue > 0 ? 0 : yMinValue, yMaxValue]) + .range([graphHeight, 0]) + .nice() + : scaleLog().domain([yMinValue, yMaxValue]).range([graphHeight, 0]).nice(); + const xTicks = !xScaleLogAllowed ? xScale.ticks(5) : xScale.ticks(3); + const yTicks = !yScaleLogAllowed ? yScale.ticks(5) : yScale.ticks(3); const voronoiDiagram = Delaunay.from( dataFormatted, d => xScale(d.xVal as number), @@ -552,6 +571,7 @@ export function Graph(props: Props) { stroke='#AAA' strokeWidth={1} strokeDasharray='4,8' + opacity={yScaleLogAllowed && i === 0 ? 0 : 1} /> - 0 + {yScaleLogAllowed ? '' : 0} @@ -615,6 +636,7 @@ export function Graph(props: Props) { stroke='#AAA' strokeWidth={1} strokeDasharray='4,8' + opacity={xScaleLogAllowed && i === 0 ? 0 : 1} /> {Math.abs(d) < 1 ? d : format('~s')(d).replace('G', 'B')} @@ -631,20 +653,20 @@ export function Graph(props: Props) { - {0} + {xScaleLogAllowed ? '' : 0} @@ -866,7 +889,11 @@ export function Graph(props: Props) { : UNDPColorModule.graphGray } /> - {showLabel ? ( + {showLabel && + (selectedCountries.length === 0 || + selectedCountries.indexOf( + countryData['Country or Area'], + ) !== -1) ? ( !d.IsCategorical); const sizeIndicators = indicators.filter(d => d.Sizing); @@ -252,6 +256,26 @@ export function ScatterPlotSettings(props: Props) { > Use same axes to compare between years + { + updateXScaleType(e.target.checked ? 'log' : 'linear'); + }} + > + Use log scale for x-axis + + { + updateYScaleType(e.target.checked ? 'log' : 'linear'); + }} + > + Use log scale for y-axis +
{!selectedCountryOrRegion && countries.length > 1 ? ( diff --git a/src/RegionVisualization/Visualization.tsx b/src/RegionVisualization/Visualization.tsx index 224c39a..89cb789 100644 --- a/src/RegionVisualization/Visualization.tsx +++ b/src/RegionVisualization/Visualization.tsx @@ -92,6 +92,8 @@ function VisualizationEl(props: Props) { disaggregationGraphType: 'global', disaggregationOrder: 'first', keepAxisSame: false, + xScaleType: 'linear', + yScaleType: 'linear', }; const [state, dispatch] = useReducer(Reducer, initialState); @@ -277,6 +279,20 @@ function VisualizationEl(props: Props) { payload: d, }); }; + + const updateXScaleType = (d: 'linear' | 'log') => { + dispatch({ + type: 'UPDATE_X_SCALE_TYPE', + payload: d, + }); + }; + + const updateYScaleType = (d: 'linear' | 'log') => { + dispatch({ + type: 'UPDATE_Y_SCALE_TYPE', + payload: d, + }); + }; return (
diff --git a/src/Types.ts b/src/Types.ts index 0fa2c2c..8ea4786 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -169,6 +169,8 @@ export interface CtxDataType { | 'Poverty and Inequality' | 'Resilience'; keepAxisSame: boolean; + xScaleType: 'linear' | 'log'; + yScaleType: 'linear' | 'log'; updateGraphType: ( _d: | 'scatterPlot' @@ -211,6 +213,8 @@ export interface CtxDataType { | 'Resilience', ) => void; updateKeepAxisSame: (_d: boolean) => void; + updateXScaleType: (_d: 'linear' | 'log') => void; + updateYScaleType: (_d: 'linear' | 'log') => void; } export interface CountryListType {