Merge branch 'feature/point_chart' into 'dev'

point chart, query fixes

See merge request spatial/postamates_frontend!53
dev
Anton Vlasov 2 years ago
commit e40af1b5d3

@ -17,6 +17,7 @@
"@watergis/maplibre-gl-export": "^1.3.7", "@watergis/maplibre-gl-export": "^1.3.7",
"antd": "^4.23.6", "antd": "^4.23.6",
"axios": "^1.1.3", "axios": "^1.1.3",
"chart.js": "^4.4.0",
"immer": "^9.0.19", "immer": "^9.0.19",
"immutable": "^4.3.0", "immutable": "^4.3.0",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
@ -25,6 +26,7 @@
"nanostores": "^0.7.3", "nanostores": "^0.7.3",
"react": "^18.2.0", "react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-icons": "^4.8.0", "react-icons": "^4.8.0",
"react-map-gl": "^7.0.19", "react-map-gl": "^7.0.19",

@ -0,0 +1,98 @@
import { Line } from "react-chartjs-2";
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
BarElement,
Title,
Tooltip as ChartTooltip,
Legend, PointElement, LineElement,
} from 'chart.js';
ChartJS.register(
CategoryScale,
PointElement,
LineElement,
LinearScale,
BarElement,
Title,
ChartTooltip,
Legend
);
const GRAPH_LABELS_MAP= {
target_dist_shap: "Расстояние до ближайшего постамата Мой постамат",
target_post_cnt_shap: "Кол-во точек Мой постамат*",
target_cnt_ao_mean_shap: "Средний трафик в точках Мой постамат в АО",
rival_pvz_cnt_shap: "Кол-во ПВЗ*",
rival_post_cnt_shap: "Кол-во постаматов конкурентов *",
metro_dist_shap: "Расстояние до метро",
property_price_bargains_shap: "Цена сделок жилой недвижимости*",
property_price_offers_shap: "Цена предложений жилой недвижимости*",
property_mean_floor_shap: "Средняя этажность застройки*",
property_era_shap: "Эпоха жилой недвижимости*",
flats_cnt_shap: "Кол-во квартир*",
popul_home_shap: "Численность проживающего населения*",
popul_job_shap: "Численность работающего населения*",
yndxfood_sum_shap: "Сумма заказов Яндекс.Еда*",
yndxfood_cnt_shap: "Кол-во заказов Яндекс.Еда*",
school_cnt_shap: "Кол-во школ*",
kindergar_cnt_shap: "Кол-во детсадов*",
public_stop_cnt_shap: "Кол-во остановок общ. транспорта*",
sport_center_cnt_shap: "Кол-во спортивных центров*",
pharmacy_cnt_shap: "Кол-во аптек*",
supermarket_cnt_shap: "Кол-во супермаркетов*",
supermarket_premium_cnt_shap: "Кол-во премиальных супермаркетов*",
clinic_cnt_shap: "Кол-во поликлиник*",
bank_cnt_shap: "Кол-во банков*",
reca_cnt_shap: "Кол-во точек общепита*",
lab_cnt_shap: "Кол-во лабораторий*",
culture_cnt_shap: "Кол-во объектов культуры*",
attraction_cnt_shap: "Кол-во достопримечательностей*",
mfc_cnt_shap: "Кол-во МФЦ*",
bc_cnt_shap: "Кол-во бизнес-центров*",
tc_cnt_shap: "Кол-во торговых центров*",
business_activity_shap: "Бизнес активность",
}
export const PointChart = ({ point }) => {
const options = {
indexAxis: 'y',
elements: {
bar: {
borderWidth: 0,
borderRadius: 5,
pointStyle: 'circle'
},
},
plugins: {
legend: {
display: false
}
},
scales: {
x: {
title: {
display: true,
text: 'Вклад в прогноз, %'
},
}
}
};
const labels = Object.keys(GRAPH_LABELS_MAP).sort((a, b) => {
if (Math.abs(point[a]) < Math.abs(point[b])) return 1;
else return -1;
}).slice(0, 15);
labels.map((l) => console.log(l, point[l]))
const data = {
labels: labels.map((l) => GRAPH_LABELS_MAP[l]),
datasets: [
{
data: labels.map((l) => point[l]),
backgroundColor: labels.map((l) => point[l]).map(v => v <= 0 ? '#CC2500' : '#278211'),
hoverBackgroundColor: labels.map((l) => point[l]).map(v => v <= 0 ? '#F22C00' : '#2DB20C'),
showLine: false
},
],
};
return <Line options={options} data={data} />
}

@ -12,6 +12,7 @@ import {getFilteredGroups, isNil} from "../../../utils.js";
import { useGetRegions } from "../../../components/RegionSelect.jsx"; import { useGetRegions } from "../../../components/RegionSelect.jsx";
import {useOtherGroups, usePostamatesAndPvzGroups} from "../../../api.js"; import {useOtherGroups, usePostamatesAndPvzGroups} from "../../../api.js";
import {useMemo} from "react"; import {useMemo} from "react";
import { TrafficModal } from "../../TrafficModal.jsx";
const getRivalsName = (feature) => { const getRivalsName = (feature) => {
const { data: postamatesAndPvzGroups } = usePostamatesAndPvzGroups(); const { data: postamatesAndPvzGroups } = usePostamatesAndPvzGroups();
@ -78,6 +79,10 @@ export const FeatureProperties = ({ feature, dynamicStatus, postamatId, point })
const getValue = ({ field, render, empty, type, fallbackField }) => { const getValue = ({ field, render, empty, type, fallbackField }) => {
let value = point ? point[field] : feature.properties[field]; let value = point ? point[field] : feature.properties[field];
if (field === "prediction_current") {
value = <TrafficModal point={point} />;
}
if (field === "category_id") { if (field === "category_id") {
value = name; value = name;
} }

@ -0,0 +1,93 @@
import { Button, Col, Divider, Modal, Popover, Row, Tabs } from "antd";
import { useState } from "react";
import { BiStats } from "react-icons/all.js";
import { twMerge } from "tailwind-merge";
import { PointChart } from "./PointChart.jsx";
const ChartHelp = () => {
return (
<div className="w-[200px]">
График показывает топ-15 факторов, которые оказывают наибольшее влияние на прогнозный трафик в определённой точке.<br/><br/>
Факторы могут оказывать положительное или отрицательное влияние.<br/><br/>
Чем больше влияния оказывает фактор на прогнозный трафик, тем ближе его значение к 100% (-100%).
</div>
)
}
const getTabs = (point) => {
return [
{
key: '1',
label: 'Выбранная точка',
children: (
<div>
<div className="flex flex-col gap-2">
<Row className={twMerge("p-1")}>
<Col className={"font-semibold"} span={12}>
Адрес точки:
</Col>
<Col span={12}>{point.address}</Col>
</Row>
<Row className={twMerge("p-1")}>
<Col className={"font-semibold"} span={12}>
Прогнозный траффик:
</Col>
<Col span={12}>{point.prediction_current}</Col>
</Row>
</div>
<Divider />
<PointChart point={point} />
<p>* - в окрестности</p>
<Popover
content={<ChartHelp autoFocus={true}/>}
trigger="click"
placement="leftBottom"
color="#ffffff"
>
<Button type="text" className="text-[#1890FF] p-0">Как читать график?</Button>
</Popover>
</div>
),
},
{
key: '2',
label: 'Среднее по всем точкам',
children: '',
},
];
}
export const TrafficModal = ({point}) => {
const [isOpened, setIsOpened] = useState(false);
const getFooter = () => {
return [
<Button
key="close-button"
type="primary"
onClick={() => setIsOpened(false)}
>
Закрыть
</Button>,
]
}
return (
<div className="flex items-center">
{point.prediction_current}
<Button className="flex justify-center items-center h-6 ml-1 p-1" type="primary" onClick={() => setIsOpened(true)}>
<BiStats />
</Button>
<Modal
open={isOpened}
title="Вклад факторов в прогноз трафика"
onCancel={() => setIsOpened(false)}
width={800}
footer={getFooter()}
style={{ top: "15px" }}
>
<Tabs defaultActiveKey="1" items={getTabs(point)} />
</Modal>
</div>
);
}

@ -13,7 +13,7 @@ export function SignOut() {
}; };
const { data } = useQuery(["profile"], async () => { const { data } = useQuery(["profile"], async () => {
const { data } = await api.get("/accounts/profile"); const { data } = await api.get("/accounts/profile/");
return data; return data;
}); });

@ -115,7 +115,10 @@ export const useGetTotalInitialPointsCount = () => {
return await getPoints(params, null, dbTable); return await getPoints(params, null, dbTable);
}, },
{ select: (data) => data.count } {
select: (data) => data.count,
refetchOnWindowFocus: false,
refetchOnMount: false }
); );
}; };
@ -160,7 +163,12 @@ export const useGetFilteredPendingPointsCount = (isMerge) => {
return await getPoints(params, region, dbTable); return await getPoints(params, region, dbTable);
}, },
{ select: (data) => data.count, keepPreviousData: true } {
select: (data) => data.count,
keepPreviousData: true,
refetchOnWindowFocus: false,
refetchOnMount: false
}
); );
}; };
@ -349,6 +357,10 @@ export const useGetPopupPoints = (features) => {
return data.results; return data.results;
}, },
{
refetchOnWindowFocus: false,
refetchOnMount: false
}
); );
return { data, isLoading: isInitialLoading || isFetching }; return { data, isLoading: isInitialLoading || isFetching };

@ -27,7 +27,7 @@ export const useGetRegions = () => {
return useQuery( return useQuery(
["regions"], ["regions"],
async () => { async () => {
const { data } = await api.get("/api/ao_rayons"); const { data } = await api.get("/api/ao_rayons/");
return data; return data;
}, },
{ {
@ -37,6 +37,8 @@ export const useGetRegions = () => {
normalized: normalizeRegions(rawRegions), normalized: normalizeRegions(rawRegions),
}; };
}, },
refetchOnWindowFocus: false,
refetchOnMount: false
} }
); );
}; };

@ -44,6 +44,8 @@ export const usePendingTableData = (page, resetPage, pageSize, setPageSize, sort
resetPage(); resetPage();
} }
}, },
refetchOnWindowFocus: false,
refetchOnMount: false
} }
); );

@ -47,7 +47,7 @@ export const useMergeTableData = (fullData, onPageSizeChange) => {
onPageSizeChange(PAGE_SIZE + 1); onPageSizeChange(PAGE_SIZE + 1);
setMergedData({ setMergedData({
count: fullData.count + 1, count: fullData?.count + 1,
results: [clickedPointData.results[0], ...fullData.results], results: [clickedPointData.results[0], ...fullData.results],
}); });
}, [clickedPointData, fullData]); }, [clickedPointData, fullData]);

Loading…
Cancel
Save