Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/districtr/districtr-v2 into…
Browse files Browse the repository at this point in the history
… feat/chart-enhancements
  • Loading branch information
mariogiampieri committed Nov 19, 2024
2 parents 013bfff + b5d2d14 commit f52775e
Show file tree
Hide file tree
Showing 20 changed files with 498 additions and 125 deletions.
36 changes: 20 additions & 16 deletions app/src/app/components/sidebar/Evaluation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import {
stdDevArray,
stdDevColors,
sumArray,
} from '@/app/utils/summaryStats';
import { interpolateBlues, interpolateGreys } from 'd3-scale-chromatic';
} from '@utils/summaryStats';
import {interpolateBlues, interpolateGreys} from 'd3-scale-chromatic';

type EvalModes = 'share' | 'count' | 'totpop';
type ColumnConfiguration<T extends Record<string, any>> = Array<{label: string; column: keyof T}>;
Expand Down Expand Up @@ -127,27 +127,27 @@ const Evaluation: React.FC<EvaluationProps> = ({columnConfig = defaultColumnConf
);

const {
unassigned,
maxValues
// averages,
unassigned,
maxValues,
// averages,
// stdDevs
} = useMemo(() => {
if (!data?.results || !totPop) {
return {};
}
let maxValues: Record<string, number> = {}
let maxValues: Record<string, number> = {};

let unassigned: Record<string, number> = {
...totPop,
zone: -999,
total: getEntryTotal(totPop),
};
P1ZoneSummaryStatsKeys.forEach(key => {
let total = unassigned[key];
maxValues[key] = -Math.pow(10,12)
maxValues[key] = -Math.pow(10, 12);
data.results.forEach(row => {
total -= row[key];
maxValues[key] = Math.max(row[key], maxValues[key])
maxValues[key] = Math.max(row[key], maxValues[key]);
});
unassigned[`${key}_pct`] = total / unassigned[key];
unassigned[key] = total;
Expand All @@ -160,9 +160,9 @@ const Evaluation: React.FC<EvaluationProps> = ({columnConfig = defaultColumnConf
// stdDevs[key] = stdDevArray(values);
// });
return {
unassigned,
maxValues
// averages,
unassigned,
maxValues,
// averages,
// stdDevs
};
}, [data?.results, totPop]);
Expand Down Expand Up @@ -292,10 +292,14 @@ const Evaluation: React.FC<EvaluationProps> = ({columnConfig = defaultColumnConf
</td>
{columnConfig.map((f, i) => {
const column = columnGetter(f.column);
const colorValue = evalMode === 'count' ? row[column]/maxValues[column] : row[column]
const backgroundColor = (colorBg && !isUnassigned)
? interpolateGreys(colorValue).replace('rgb', 'rgba').replace(')', ",0.5)")
: 'initial'
const colorValue =
evalMode === 'count' ? row[column] / maxValues[column] : row[column];
const backgroundColor =
colorBg && !isUnassigned
? interpolateGreys(colorValue)
.replace('rgb', 'rgba')
.replace(')', ',0.5)')
: 'initial';
return (
<td
className="py-2 px-4 text-right"
Expand Down
4 changes: 2 additions & 2 deletions app/src/app/components/sidebar/Layers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ export default function Layers() {
disabled={!parentsAreBroken}
onClick={() => toggleHighlightBrokenDistricts()}
>
Highlight Broken Voter Districts
Highlight broken precincts
</CheckboxGroup.Item>
<CheckboxGroup.Item value="higlightUnassigned" onClick={() => setMapOptions({
higlightUnassigned: !mapOptions.higlightUnassigned
})}>
Highlight Unassigned Districts
Highlight unassigned units
</CheckboxGroup.Item>
<CheckboxGroup.Item value="4" onClick={() => toggleLockAllAreas()}>
Lock All Painted Areas
Expand Down
14 changes: 7 additions & 7 deletions app/src/app/utils/api/apiHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,9 @@ export const getP1SummaryStats: (
) => Promise<SummaryStatsResult<CleanedP1ZoneSummaryStats[]>> = async mapDocument => {
if (mapDocument) {
return await axios
.get<SummaryStatsResult<P1ZoneSummaryStats[]>>(
`${process.env.NEXT_PUBLIC_API_URL}/api/document/${mapDocument.document_id}/P1`
)
.get<
SummaryStatsResult<P1ZoneSummaryStats[]>
>(`${process.env.NEXT_PUBLIC_API_URL}/api/document/${mapDocument.document_id}/P1`)
.then(res => {
const results = res.data.results.map(row => {
const total = getEntryTotal(row);
Expand Down Expand Up @@ -248,12 +248,12 @@ export const getP1SummaryStats: (
*/
export const getP1TotPopSummaryStats: (
mapDocument: DocumentObject | null
) => Promise<SummaryStatsResult<P1TotPopSummaryStats[]>> = async mapDocument => {
) => Promise<SummaryStatsResult<P1TotPopSummaryStats>> = async mapDocument => {
if (mapDocument) {
return await axios
.get<SummaryStatsResult<P1TotPopSummaryStats[]>>(
`${process.env.NEXT_PUBLIC_API_URL}/api/districtrmap/summary_stats/P1TOTPOP/${mapDocument.parent_layer}`
)
.get<
SummaryStatsResult<P1TotPopSummaryStats>
>(`${process.env.NEXT_PUBLIC_API_URL}/api/districtrmap/summary_stats/P1/${mapDocument.parent_layer}`)
.then(res => res.data);
} else {
throw new Error('No document provided');
Expand Down
4 changes: 2 additions & 2 deletions app/src/app/utils/api/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,13 @@ fetchAssignments.subscribe(assignments => {
}
});

export const fetchTotPop = new QueryObserver<SummaryStatsResult<P1TotPopSummaryStats[]> | null>(
export const fetchTotPop = new QueryObserver<SummaryStatsResult<P1TotPopSummaryStats> | null>(
queryClient,
{
queryKey: ['gerrydb_tot_pop'],
queryFn: getNullableParamQuery<
MapStore['mapDocument'],
SummaryStatsResult<P1TotPopSummaryStats[]>
SummaryStatsResult<P1TotPopSummaryStats>
>(getP1TotPopSummaryStats),
}
);
Expand Down
12 changes: 7 additions & 5 deletions app/src/app/utils/events/mapEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,17 +120,19 @@ export const handleMapMouseLeave = (
e: MapLayerMouseEvent | MapLayerTouchEvent,
map: MapLibreMap | null
) => {
const mapStore = useMapStore.getState();
const activeTool = mapStore.activeTool;
const sourceLayer = mapStore.mapDocument?.parent_layer;
const setHoverFeatures = mapStore.setHoverFeatures;
const {setHoverFeatures, setIsPainting} = useMapStore.getState();
setHoverFeatures([]);
setIsPainting(false)
};

export const handleMapMouseOut = (
e: MapLayerMouseEvent | MapLayerTouchEvent,
map: MapLibreMap | null
) => {};
) => {
const {setHoverFeatures, setIsPainting} = useMapStore.getState();
setHoverFeatures([]);
setIsPainting(false)
};

export const handleMapMouseMove = (
e: MapLayerMouseEvent | MapLayerTouchEvent,
Expand Down
109 changes: 109 additions & 0 deletions backend/app/alembic/versions/2494caf34886_soft_delete_districtrmap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""soft delete districtrmap
Revision ID: 2494caf34886
Revises: f86991e63a62
Create Date: 2024-11-18 09:37:23.352006
"""

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = "2494caf34886"
down_revision: Union[str, None] = "f86991e63a62"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.add_column(
"districtrmap",
sa.Column("visible", sa.Boolean(), nullable=False, server_default="true"),
)
op.execute(
sa.text(
"""
CREATE OR REPLACE FUNCTION create_districtr_map(
map_name VARCHAR,
gerrydb_table_name VARCHAR,
num_districts INTEGER,
tiles_s3_path VARCHAR,
parent_layer_name VARCHAR,
child_layer_name VARCHAR,
visibility BOOLEAN DEFAULT TRUE
)
RETURNS UUID AS $$
DECLARE
inserted_districtr_uuid UUID;
BEGIN
INSERT INTO districtrmap (
created_at,
uuid,
name,
gerrydb_table_name,
num_districts,
tiles_s3_path,
parent_layer,
child_layer,
visible
)
VALUES (
now(),
gen_random_uuid(),
map_name,
gerrydb_table_name,
num_districts,
tiles_s3_path,
parent_layer_name,
child_layer_name,
visibility
)
RETURNING uuid INTO inserted_districtr_uuid;
RETURN inserted_districtr_uuid;
END;
$$ LANGUAGE plpgsql;
"""
)
)


def downgrade() -> None:
op.drop_column("districtrmap", "visible")
op.execute(
sa.text(
"""
CREATE OR REPLACE FUNCTION create_districtr_map(
map_name VARCHAR,
gerrydb_table_name VARCHAR,
num_districts INTEGER,
tiles_s3_path VARCHAR,
parent_layer_name VARCHAR,
child_layer_name VARCHAR
)
RETURNS UUID AS $$
DECLARE
inserted_districtr_uuid UUID;
BEGIN
INSERT INTO districtrmap (
created_at,
uuid,
name,
gerrydb_table_name,
num_districts,
tiles_s3_path,
parent_layer,
child_layer
)
VALUES (now(), gen_random_uuid(), $1, $2, $3, $4, $5, $6)
RETURNING uuid INTO inserted_districtr_uuid;
RETURN inserted_districtr_uuid;
END;
$$ LANGUAGE plpgsql;
"""
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
revision: str = "c3541f016d35"
down_revision: Union[str, None] = "5d9f7335f98a"
branch_labels: Union[str, Sequence[str], None] = None
depends_on = "5d9f7335f98a"
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
Expand Down
11 changes: 9 additions & 2 deletions backend/app/alembic/versions/f86991e63a62_summary_stat_udfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,19 @@


def upgrade() -> None:
for udf in ["summary_stats_p1.sql", "summary_stats_pop_totals.sql"]:
for udf in [
"summary_stats_p1.sql",
"summary_stats_p1_totals.sql",
"summary_stats_p4.sql",
"summary_stats_p4_totals.sql",
]:
with Path(SQL_DIR, udf).open() as f:
sql = f.read()
op.execute(sql)


def downgrade() -> None:
op.execute("DROP FUNCTION IF EXISTS get_summary_stats_p1")
op.execute("DROP FUNCTION IF EXISTS get_summary_stats_pop_totals")
op.execute("DROP FUNCTION IF EXISTS get_summary_p1_totals")
op.execute("DROP FUNCTION IF EXISTS get_summary_stats_p4")
op.execute("DROP FUNCTION IF EXISTS get_summary_p4_totals")
27 changes: 16 additions & 11 deletions backend/app/main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from fastapi import FastAPI, status, Depends, HTTPException, Query
from sqlalchemy import text
from sqlalchemy.exc import ProgrammingError
from sqlmodel import Session, String, select
from sqlmodel import Session, String, select, true
from starlette.middleware.cors import CORSMiddleware
from sqlalchemy.dialects.postgresql import insert
import logging
Expand Down Expand Up @@ -29,7 +29,8 @@
SummaryStatisticType,
SummaryStatsP1,
PopulationStatsP1,
GerryDbSummaryStatisticType,
SummaryStatsP4,
PopulationStatsP4,
)

if settings.ENVIRONMENT == "production":
Expand Down Expand Up @@ -336,10 +337,12 @@ async def get_summary_stat(
),
SummaryStatsP1,
),
"P1TOTPOP": {
text("SELECT * from get_summary_stats_pop_totals(:document_id)"),
PopulationStatsP1,
},
"P4": (
text(
"SELECT * from get_summary_stats_p4(:document_id) WHERE zone is not null"
),
SummaryStatsP4,
),
}[summary_stat]
except KeyError:
raise HTTPException(
Expand All @@ -351,7 +354,7 @@ async def get_summary_stat(
results = session.execute(stmt, {"document_id": document_id}).fetchall()
return {
"summary_stat": _summary_stat.value,
"results": [SummaryStatsModel.from_orm(row) for row in results],
"results": [SummaryStatsModel.model_validate(row) for row in results],
}
except ProgrammingError as e:
logger.error(e)
Expand All @@ -368,7 +371,7 @@ async def get_gerrydb_summary_stat(
summary_stat: str, gerrydb_table: str, session: Session = Depends(get_session)
):
try:
_summary_stat = GerryDbSummaryStatisticType[summary_stat]
_summary_stat = SummaryStatisticType[summary_stat]
except ValueError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
Expand All @@ -377,7 +380,8 @@ async def get_gerrydb_summary_stat(

try:
summary_stat_udf, SummaryStatsModel = {
"P1TOTPOP": ("get_summary_stats_pop_totals", PopulationStatsP1),
"P1": ("get_summary_p1_totals", PopulationStatsP1),
"P4": ("get_summary_p4_totals", PopulationStatsP4),
}[summary_stat]
except KeyError:
raise HTTPException(
Expand All @@ -392,10 +396,10 @@ async def get_gerrydb_summary_stat(
bindparam(key="gerrydb_table", type_=String),
)
try:
results = session.execute(stmt, {"gerrydb_table": gerrydb_table}).fetchall()
results = session.execute(stmt, {"gerrydb_table": gerrydb_table}).fetchone()
return {
"summary_stat": _summary_stat.value,
"results": [SummaryStatsModel.from_orm(row) for row in results],
"results": SummaryStatsModel.model_validate(results),
}
except ProgrammingError as e:
logger.error(e)
Expand All @@ -416,6 +420,7 @@ async def get_projects(
):
gerrydb_views = session.exec(
select(DistrictrMap)
.filter(DistrictrMap.visible == true()) # pyright: ignore
.order_by(DistrictrMap.created_at.asc()) # pyright: ignore
.offset(offset)
.limit(limit)
Expand Down
Loading

0 comments on commit f52775e

Please sign in to comment.