From ac78e3d85e94e765559f3804d4b09c3c0cb03d5a Mon Sep 17 00:00:00 2001 From: Michael Hudson Nkotagu Date: Tue, 6 Aug 2024 13:26:13 +0300 Subject: [PATCH 1/3] feat: Add Stacked Chart Scope to Hurumap package --- .../HURUmap/Chart/configureScope.js | 13 ++----- .../src/Scope}/StackedChartScope.js | 34 +++++++++++-------- packages/hurumap-core/src/Scope/index.js | 2 ++ 3 files changed, 24 insertions(+), 25 deletions(-) rename {apps/pesayetu/src/components/HURUmap/Chart => packages/hurumap-core/src/Scope}/StackedChartScope.js (97%) diff --git a/apps/pesayetu/src/components/HURUmap/Chart/configureScope.js b/apps/pesayetu/src/components/HURUmap/Chart/configureScope.js index b0bd09799..4e023c078 100644 --- a/apps/pesayetu/src/components/HURUmap/Chart/configureScope.js +++ b/apps/pesayetu/src/components/HURUmap/Chart/configureScope.js @@ -1,6 +1,5 @@ import { Scope } from "@hurumap/core"; -import StackedChartScope from "./StackedChartScope"; import VerticalStackedChartScope from "./VerticalStackedChartScope"; import { hurumapArgs } from "@/pesayetu/config"; @@ -13,6 +12,7 @@ const { MultiLineChartScope, TreemapChartScope, VerticalBarChartScope, + StackedChartScope, } = Scope; export default function configureScope( @@ -74,16 +74,7 @@ export default function configureScope( isCompare, ); } else { - vegaSpec = StackedChartScope( - indicator?.data, - indicator?.metadata, - configuration, - secondaryIndicator?.data ?? null, - showParent ? indicator?.parentData : [{}], - showParent ? secondaryIndicator?.parentData : [{}], - profileNames, - isCompare, - ); + vegaSpec = StackedChartScope(scopeOptions); } break; default: diff --git a/apps/pesayetu/src/components/HURUmap/Chart/StackedChartScope.js b/packages/hurumap-core/src/Scope/StackedChartScope.js similarity index 97% rename from apps/pesayetu/src/components/HURUmap/Chart/StackedChartScope.js rename to packages/hurumap-core/src/Scope/StackedChartScope.js index 2c4f4e055..e9d3374f5 100644 --- a/apps/pesayetu/src/components/HURUmap/Chart/StackedChartScope.js +++ b/packages/hurumap-core/src/Scope/StackedChartScope.js @@ -2,9 +2,7 @@ import merge from "deepmerge"; import Scope from "./Scope"; -import theme from "@/pesayetu/theme"; - -export default function StackedChartScope( +export default function StackedChartScope({ primaryData, metadata, config, @@ -13,7 +11,10 @@ export default function StackedChartScope( secondaryParentData, profileNames, isCompare, -) { + isMobile, + theme, + args, +}) { const { parentLabel } = config; const { primary_group: primaryGroup } = metadata; @@ -38,23 +39,28 @@ export default function StackedChartScope( ] : null; + const transform = [ + { + type: "stack", + groupby: [primaryGroup], + field: { signal: "datatype[Units]" }, + }, + ]; + return merge( - Scope( + Scope({ primaryData, metadata, config, secondaryData, primaryParentData, secondaryParentData, - "stacked", - [ - { - type: "stack", - groupby: [primaryGroup], - field: { signal: "datatype[Units]" }, - }, - ], - ), + chartType: "stacked", + transform, + isMobile, + theme, + args, + }), { signals: [ { diff --git a/packages/hurumap-core/src/Scope/index.js b/packages/hurumap-core/src/Scope/index.js index 3d54a20d3..4e6b88e57 100644 --- a/packages/hurumap-core/src/Scope/index.js +++ b/packages/hurumap-core/src/Scope/index.js @@ -3,6 +3,7 @@ import DonutChartScope from "./DonutChartScope"; import LineChartScope from "./LineChartScope"; import MultiLineChartScope from "./MultiLineChartScope"; import Scope from "./Scope"; +import StackedChartScope from "./StackedChartScope"; import TreemapChartScope from "./TreemapChartScope"; import VerticalBarChartScope from "./VerticalBarChartScope"; @@ -14,4 +15,5 @@ export default { MultiLineChartScope, VerticalBarChartScope, TreemapChartScope, + StackedChartScope, }; From 3e2a3adfe4807705e0ed3a3c12e76e9bc5982cc4 Mon Sep 17 00:00:00 2001 From: Michael Hudson Nkotagu Date: Tue, 6 Aug 2024 14:21:17 +0300 Subject: [PATCH 2/3] fix: Remove nice property to fix breaking stacked StackedChart --- packages/hurumap-core/src/Scope/StackedChartScope.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/hurumap-core/src/Scope/StackedChartScope.js b/packages/hurumap-core/src/Scope/StackedChartScope.js index e9d3374f5..b9758f804 100644 --- a/packages/hurumap-core/src/Scope/StackedChartScope.js +++ b/packages/hurumap-core/src/Scope/StackedChartScope.js @@ -94,7 +94,8 @@ export default function StackedChartScope({ ], zero: true, clamp: true, - nice: { signal: "primaryXTickCount" }, + // TODO: explore why adding nice breaks the chart with error: Error: Cycle detected in dataflow graph. + // nice: { signal: "primaryXTickCount" }, }, { name: "s_xscale", @@ -110,7 +111,8 @@ export default function StackedChartScope({ ], zero: true, clamp: true, - nice: { signal: "secondaryXTickCount" }, + // TODO: explore why adding nice breaks the chart with error: Error: Cycle detected in dataflow graph. + // nice: { signal: "secondaryXTickCount" }, }, { name: "color", From 31060ecb0ea77232a31d952b4c738d6e3b4acd66 Mon Sep 17 00:00:00 2001 From: Michael Hudson Nkotagu Date: Tue, 6 Aug 2024 14:27:49 +0300 Subject: [PATCH 3/3] feat: Import StackedChartScope in climatemappedafrica from hurumap-core --- .../HURUmap/Chart/StackedChartScope.js | 411 ------------------ .../HURUmap/Chart/configureScope.js | 14 +- 2 files changed, 2 insertions(+), 423 deletions(-) delete mode 100644 apps/climatemappedafrica/src/components/HURUmap/Chart/StackedChartScope.js diff --git a/apps/climatemappedafrica/src/components/HURUmap/Chart/StackedChartScope.js b/apps/climatemappedafrica/src/components/HURUmap/Chart/StackedChartScope.js deleted file mode 100644 index 1d4db49f6..000000000 --- a/apps/climatemappedafrica/src/components/HURUmap/Chart/StackedChartScope.js +++ /dev/null @@ -1,411 +0,0 @@ -import merge from "deepmerge"; - -import Scope from "./Scope"; - -import theme from "@/climatemappedafrica/theme"; - -export default function StackedChartScope( - primaryData, - metadata, - config, - secondaryData, - primaryParentData, - secondaryParentData, - profileNames, - isCompare, -) { - const { parentLabel } = config; - - const { primary_group: primaryGroup } = metadata; - const stackedField = config.stacked_field; - - const secondaryLegend = isCompare - ? [ - { - orient: { - value: "none", - }, - legendY: { - signal: "height + 30 ", - }, - labelLimit: 400, - legendX: { signal: "-width/2 - 30" }, - fill: "legend_secondary_scale", - labelFontWeight: "bold", - labelColor: "#666", - labelFont: theme.typography.fontFamily, - }, - ] - : null; - - return merge( - Scope( - primaryData, - metadata, - config, - secondaryData, - primaryParentData, - secondaryParentData, - "stacked", - [ - { - type: "stack", - groupby: [primaryGroup], - field: { signal: "datatype[Units]" }, - }, - ], - ), - { - signals: [ - { - name: "height", - update: "bandspace(domain('yscale').length, 0.1, 0.05) * y_step", - }, - { - name: "stackedField", - value: stackedField, - }, - ], - scales: [ - { - name: "yscale", - type: "band", - domain: { data: "primary_formatted", field: primaryGroup }, - range: { step: { signal: "y_step" } }, - padding: 0.15, - }, - { - name: "xscale", - type: "linear", - domain: { data: "primary_formatted", field: "y1" }, - domainMin: { signal: "domainMin" }, - domainMax: { signal: "domainMax" }, - range: [ - 0, - { - signal: "data('secondary').length > 1 ? width/2 - 30 : width", - }, - ], - zero: true, - clamp: true, - // nice: { signal: "primaryXTickCount" }, - }, - { - name: "s_xscale", - type: "linear", - domain: { data: "secondary_formatted", field: "y1" }, - domainMin: { signal: "domainMin" }, - domainMax: { signal: "domainMax" }, - range: [ - 0, - { - signal: "data('secondary').length > 1 ? width/2 - 30 : 0", - }, - ], - zero: true, - clamp: true, - // nice: { signal: "secondaryXTickCount" }, - }, - { - name: "color", - type: "ordinal", - range: "category", - domain: { - data: "primary_formatted", - field: stackedField, - sort: true, - }, - }, - { - name: "secondary_color", - type: "ordinal", - range: "secondary", - domain: { - data: "secondary_formatted", - field: stackedField, - }, - }, - { - name: "legend_primary_scale", - type: "ordinal", - domain: [profileNames.primary.toUpperCase()], - range: [theme.palette.primary.main], - }, - { - name: "legend_secondary_scale", - type: "ordinal", - domain: [profileNames.secondary.toUpperCase()], - range: [theme.palette.secondary.main], - }, - { - name: "parent_color_scale", - type: "ordinal", - range: "category", - domain: [parentLabel], - }, - ], - - marks: [ - { - type: "group", - name: "primary_stacked_bars", - encode: { - update: { - x: { value: 0 }, - y: { signal: "chartY" }, - height: { signal: "height" }, - width: { - signal: "data('secondary').length > 1 ? (width / 2 ) : width", - }, - }, - }, - axes: [ - { - orient: "left", - scale: "yscale", - domainOpacity: 0.5, - tickSize: 0, - labelPadding: 6, - zindex: 1, - }, - { - orient: "bottom", - scale: "xscale", - bandPosition: 0, - domainOpacity: 0.5, - tickSize: 0, - format: { signal: "numberFormat[Units]" }, - grid: true, - labelPadding: 6, - tickCount: { signal: "primaryXTickCount" }, - }, - ], - legends: [ - { - fill: "color", - orient: "top", - direction: "horizontal", - strokeColor: "transparent", - labelFont: theme.typography.fontFamily, - encode: { - labels: { - interactive: true, - update: { - fontSize: { value: 11 }, - fill: { value: theme.palette.chart.text.primary }, - }, - }, - symbols: { - update: { - stroke: { value: "transparent" }, - }, - }, - }, - }, - ], - marks: [ - { - name: "bars", - from: { data: "primary_formatted" }, - type: "rect", - encode: { - enter: { - y: { scale: "yscale", field: { signal: "mainGroup" } }, - height: { scale: "yscale", band: 1 }, - x: { scale: "xscale", field: "y0" }, - x2: { scale: "xscale", field: "y1" }, - fill: { scale: "color", field: stackedField }, - }, - update: { - y: { scale: "yscale", field: { signal: "mainGroup" } }, - fillOpacity: { value: 1 }, - x: { scale: "xscale", field: "y0" }, - x2: { scale: "xscale", field: "y1" }, - tooltip: { - signal: - "{'group': datum[mainGroup], 'count': format(datum.count, numberFormat.value), 'category': datum[stackedField]}", - }, - }, - }, - }, - ], - }, - { - type: "group", - name: "primary_parent_rule", - encode: { - update: { - x: { value: 0 }, - y: { signal: "chartY" }, - height: { signal: "height" }, - }, - }, - legends: - primaryParentData?.length > 1 - ? [ - { - fill: "parent_color_scale", - orient: "none", - legendX: { signal: "width - 90" }, - legendY: { value: -40 }, - labelFont: theme.typography.fontFamily, - labelColor: theme.palette.chart.text.primary, - encode: { - symbols: { - shape: { value: "stroke" }, - update: { - shape: { value: "stroke" }, - size: { value: 500 }, - stroke: { value: theme.palette.chart.text.primary }, - strokeDash: { value: [2, 2] }, - }, - }, - }, - }, - ] - : null, - marks: [ - { - name: "parent", - from: { data: "primary_parent_formatted" }, - type: "rule", - encode: { - update: { - y: { scale: "yscale", field: { signal: "mainGroup" } }, - y2: { - scale: "yscale", - field: { signal: "mainGroup" }, - offset: { signal: "y_step - 5" }, - }, - x: { scale: "xscale", field: "y1" }, - x2: { scale: "xscale", field: "y1" }, - stroke: { - signal: - "datum.primary && (datum[datatype[Units]] > datum.primary[datatype[Units]]) ? grey_mark: white_mark", - }, - strokeWidth: { value: 1 }, - strokeDash: { value: [2, 2] }, - }, - }, - }, - ], - }, - { - type: "group", - name: "secondary_bars", - encode: { - update: { - x: { signal: "(width / 2 ) + 30" }, - y: { signal: "chartY" }, - height: { - signal: "data('secondary').length > 1 ? height : 0", - }, - }, - }, - axes: [ - { - orient: "bottom", - scale: "s_xscale", - bandPosition: 0, - domainOpacity: 0.5, - tickSize: 0, - format: { signal: "numberFormat[Units]" }, - grid: true, - labelPadding: 6, - tickCount: { signal: "secondaryXTickCount" }, - }, - ], - legends: - isCompare && secondaryData?.length > 1 - ? [ - ...secondaryLegend, - { - fill: "secondary_color", - orient: "top", - direction: "horizontal", - strokeColor: "transparent", - labelFont: theme.typography.fontFamily, - encode: { - labels: { - interactive: true, - update: { - fontSize: { value: 11 }, - fill: { value: theme.palette.chart.text.primary }, - }, - }, - symbols: { - update: { - stroke: { value: "transparent" }, - }, - }, - }, - }, - ] - : secondaryLegend, - marks: [ - { - name: "secondary_bars", - from: { data: "secondary_formatted" }, - type: "rect", - encode: { - enter: { - y: { scale: "yscale", field: { signal: "mainGroup" } }, - height: { scale: "yscale", band: 1 }, - x: { scale: "s_xscale", field: "y0" }, - x2: { scale: "s_xscale", field: "y1" }, - fill: { scale: "secondary_color", field: stackedField }, - }, - update: { - fillOpacity: { value: 1 }, - y: { scale: "yscale", field: { signal: "mainGroup" } }, - x: { scale: "s_xscale", field: "y0" }, - tooltip: { - signal: - "{'group': datum[mainGroup], 'count': format(datum.count, numberFormat.value), 'category': datum[stackedField]}", - }, - }, - }, - }, - ], - }, - { - type: "group", - name: "secondary_parent_rule", - encode: { - update: { - x: { signal: "(width / 2 ) + 30" }, - height: { - signal: "data('secondary').length > 1 ? height : 0", - }, - }, - }, - marks: [ - { - name: "secondary_parent", - from: { data: "secondary_parent_formatted" }, - type: "rule", - encode: { - update: { - y: { scale: "yscale", field: { signal: "mainGroup" } }, - y2: { - scale: "yscale", - field: { signal: "mainGroup" }, - offset: { signal: "y_step - 5" }, - }, - x: { scale: "s_xscale", field: "y1" }, - x2: { scale: "s_xscale", field: "y1" }, - stroke: { - signal: - "datum.secondary && (datum[datatype[Units]] > datum.secondary[datatype[Units]]) ? grey_mark: white_mark", - }, - strokeWidth: { value: 1 }, - strokeDash: { value: [2, 2] }, - }, - }, - }, - ], - }, - ], - }, - ); -} diff --git a/apps/climatemappedafrica/src/components/HURUmap/Chart/configureScope.js b/apps/climatemappedafrica/src/components/HURUmap/Chart/configureScope.js index 35efd87ff..e2b25b381 100644 --- a/apps/climatemappedafrica/src/components/HURUmap/Chart/configureScope.js +++ b/apps/climatemappedafrica/src/components/HURUmap/Chart/configureScope.js @@ -1,7 +1,5 @@ import { Scope } from "@hurumap/core"; -import StackedChartScope from "./StackedChartScope"; - import { hurumapArgs } from "@/climatemappedafrica/config"; import theme from "@/climatemappedafrica/theme"; @@ -13,6 +11,7 @@ const { TreemapChartScope, VerticalBarChartScope, VerticalStackedChartScope, + StackedChartScope, } = Scope; export default function configureScope( @@ -66,16 +65,7 @@ export default function configureScope( if (isMobile) { vegaSpec = VerticalStackedChartScope(scopeOptions); } else { - vegaSpec = StackedChartScope( - indicator?.data, - indicator?.metadata, - configuration, - secondaryIndicator?.data ?? null, - showParent ? indicator?.parentData : [{}], - showParent ? secondaryIndicator?.parentData : [{}], - profileNames, - isCompare, - ); + vegaSpec = StackedChartScope(scopeOptions); } break; default: