-
-
Notifications
You must be signed in to change notification settings - Fork 0
fix: move null is zero to data source, fix bugs #340
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
Changes from all commits
b0e2850
8115d80
c4d7e3b
2434a34
ec7ce72
f9b198d
0845a5f
da79fe8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| /* eslint-disable @typescript-eslint/no-explicit-any */ | ||
| import type { Kysely } from "kysely"; | ||
|
|
||
| export async function up(db: Kysely<any>): Promise<void> { | ||
| await db.schema | ||
| .alterTable("dataSource") | ||
| .addColumn("nullIsZero", "boolean", (col) => col.defaultTo(false)) | ||
| .execute(); | ||
| } | ||
|
|
||
| export async function down(db: Kysely<any>): Promise<void> { | ||
| await db.schema.alterTable("dataSource").dropColumn("nullIsZero").execute(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -168,8 +168,6 @@ export default function VisualisationPanel({ | |
| dataSource?.geocodingConfig, | ||
| viewConfig.areaSetGroupCode, | ||
| ); | ||
| const showEmptyZeroSwitch = !isCount && columnOneIsNumber; | ||
|
|
||
| const showStyle = !viewConfig.areaDataSecondaryColumn; | ||
| const canSelectColorScale = isCount || columnOneIsNumber; | ||
| const canSelectColorScheme = canSelectColorScale && !isCategorical; | ||
|
|
@@ -443,25 +441,6 @@ export default function VisualisationPanel({ | |
| </Select> | ||
| </> | ||
| )} | ||
|
|
||
| {showEmptyZeroSwitch && ( | ||
| <div className="col-span-2 flex items-center gap-2"> | ||
| <Label | ||
| htmlFor="choropleth-empty-zero-switch" | ||
| className="text-sm text-muted-foreground font-normal" | ||
| > | ||
| Treat empty values as zero | ||
| </Label> | ||
|
|
||
| <Switch | ||
| id="choropleth-empty-zero-switch" | ||
| checked={Boolean(viewConfig.areaDataNullIsZero)} | ||
| onCheckedChange={(v) => | ||
| updateViewConfig({ areaDataNullIsZero: v }) | ||
| } | ||
| /> | ||
| </div> | ||
| )} | ||
| </div> | ||
| {!viewConfig.areaDataSourceId && ( | ||
| <div className="flex items-center gap-2"> | ||
|
|
@@ -532,7 +511,7 @@ export default function VisualisationPanel({ | |
| } | ||
| > | ||
| <SelectTrigger | ||
| className="w-full min-w-0" | ||
| className={cn("w-full min-w-0", SELECT_TO_BUTTON_CLASSES)} | ||
|
||
| id="color-scale-type-select" | ||
| > | ||
| <SelectValue placeholder="Choose color scale..."> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,7 +45,6 @@ export const getAreaStats = async ({ | |
| calculationType, | ||
| column, | ||
| secondaryColumn, | ||
| nullIsZero, | ||
| includeColumns, | ||
| boundingBox = null, | ||
| }: { | ||
|
|
@@ -54,7 +53,6 @@ export const getAreaStats = async ({ | |
| calculationType: CalculationType; | ||
| column: string; | ||
| secondaryColumn?: string; | ||
| nullIsZero?: boolean; | ||
| includeColumns?: string[] | null; | ||
| boundingBox?: BoundingBox | null; | ||
| }): Promise<AreaStats> => { | ||
|
|
@@ -110,7 +108,6 @@ export const getAreaStats = async ({ | |
| areaSetCode, | ||
| calculationType, | ||
| column, | ||
| nullIsZero, | ||
| boundingBox, | ||
| }); | ||
| const valueRange = getValueRange(primaryStats.stats); | ||
|
|
@@ -128,7 +125,6 @@ export const getAreaStats = async ({ | |
| areaSetCode, | ||
| calculationType, | ||
| column: secondaryColumn, | ||
| nullIsZero, | ||
| boundingBox, | ||
| }); | ||
| const secondaryValueRange = getValueRange(secondaryStats.stats); | ||
|
|
@@ -225,6 +221,7 @@ export const getMaxColumnByArea = async ( | |
| ${caseWhen.end()} AS max_column | ||
| FROM data_record | ||
| WHERE data_source_id = ${dataSourceId} | ||
| AND geocode_result->'areas'->>${areaSetCode} IS NOT NULL | ||
| AND ${getBoundingBoxSQL(boundingBox)} | ||
| AND ${hasNonNullValueCondition} | ||
| AND ${caseWhen.end()} IS NOT NULL | ||
|
|
@@ -254,32 +251,36 @@ const getColumnValueByArea = async ({ | |
| areaSetCode, | ||
| calculationType, | ||
| column, | ||
| nullIsZero, | ||
| boundingBox, | ||
| }: { | ||
| dataSource: DataSource; | ||
| areaSetCode: AreaSetCode; | ||
| calculationType: CalculationType; | ||
| column: string; | ||
| nullIsZero: boolean | undefined; | ||
| boundingBox: BoundingBox | null; | ||
| }) => { | ||
| const columnDef = dataSource.columnDefs.find((c) => c.name === column); | ||
| if (!columnDef) { | ||
| throw new Error(`Data source column not found: ${column}`); | ||
| } | ||
|
|
||
| // Coalesce empty values to 0 if set | ||
| const numberSelect = nullIsZero | ||
| // Coalesce numeric empty values to 0 if set | ||
| const numberSelect = dataSource.nullIsZero | ||
| ? sql`(COALESCE(NULLIF(json->>${column}, ''), '0'))::float` | ||
| : sql`(NULLIF(json->>${column}, ''))::float`; | ||
|
Comment on lines
+267
to
270
|
||
|
|
||
| // Coalesce mode empty values to 0 if the column is numeric and nullIsZero is set | ||
| const modeSelect = | ||
| columnDef.type === ColumnType.Number && dataSource.nullIsZero | ||
| ? sql`MODE () WITHIN GROUP (ORDER BY COALESCE(NULLIF(json->>${column}, ''), '0'))` | ||
| : sql`MODE () WITHIN GROUP (ORDER BY NULLIF(json->>${column}, ''))`; | ||
|
Comment on lines
+272
to
+276
|
||
|
|
||
| // Select is always MODE for ColumnType !== Number | ||
| const isMode = | ||
| calculationType === CalculationType.Mode || | ||
| columnDef.type !== ColumnType.Number; | ||
| const valueSelect = isMode | ||
| ? sql`MODE () WITHIN GROUP (ORDER BY json->>${column})`.as("value") | ||
| ? modeSelect.as("value") | ||
| : db.fn(calculationType, [numberSelect]).as("value"); | ||
|
|
||
| const query = db | ||
|
|
@@ -289,6 +290,7 @@ const getColumnValueByArea = async ({ | |
| valueSelect, | ||
| ]) | ||
| .where("dataRecord.dataSourceId", "=", dataSource.id) | ||
| .where(sql<boolean>`geocode_result->'areas'->>${areaSetCode} IS NOT NULL`) | ||
| .where(getBoundingBoxSQL(boundingBox)) | ||
| .groupBy("areaCode"); | ||
|
|
||
|
|
@@ -310,6 +312,7 @@ export const getRecordCountByArea = async ( | |
| ({ fn }) => fn.countAll().as("value"), | ||
| ]) | ||
| .where("dataRecord.dataSourceId", "=", dataSourceId) | ||
| .where(sql<boolean>`geocode_result->'areas'->>${areaSetCode} IS NOT NULL`) | ||
| .where(getBoundingBoxSQL(boundingBox)) | ||
| .groupBy("areaCode"); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -239,6 +239,7 @@ export const dataSourceRouter = router({ | |
| dateFormat: input.dateFormat, | ||
| public: input.public, | ||
| naIsNull: input.naIsNull, | ||
| nullIsZero: input.nullIsZero, | ||
| } as DataSourceUpdate; | ||
|
Comment on lines
239
to
243
|
||
|
|
||
| logger.info( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
hasValueLabelsis true, keys are filtered to those within[minValue, maxValue]. If none of the configured labels fall in-range, this setsnumTicksto 0 and renders no tick labels (but still reserves the tallerh-10layout). Consider falling back to the default tick generation when the filtered list is empty (or at least keeping a minimum of 2 ticks).