Map view for results

This commit is contained in:
Guntis Smaukstelis
2023-08-07 23:25:35 +03:00
parent 4c9af88859
commit 5ce297e96a
10 changed files with 198 additions and 42 deletions
+3 -3
View File
@@ -1,7 +1,7 @@
import moment from "moment";
import { createSignal } from "solid-js";
import { QueryResult } from "./result/QueryResult";
import { Result } from "./Result";
import { SelectCity } from "./SelectCity";
import { SelectField } from "./SelectField";
import { SelectGranularity } from "./SelectGranularity";
@@ -16,7 +16,7 @@ export function Aggregator() {
const [getStart, setStart] = createSignal(dayAgo);
const [getEnd, setEnd] = createSignal(nowRounded);
const [getField, setField] = createSignal("tempMax");
const [getKey, setKey] = createSignal("list");
const [getKey, setKey] = createSignal("max");
const [getGranularity, setGranularity] = createSignal("hour");
return (
@@ -42,7 +42,7 @@ export function Aggregator() {
</div>
</div>
<div class="column">
<Result
<QueryResult
getCities={getCities}
getStart={getStart}
getEnd={getEnd}
+21 -4
View File
@@ -14,11 +14,28 @@ export function formatDateString(str: string): string {
}
}
export interface QueryResult {
query: DataQuery;
result: {[key: string]: any};
}
export interface DataQuery {
cities: string[];
field: string;
granularity: string;
key: string;
cities: string[];
field: string;
granularity: string;
key: string;
}
export function isQueryResult(variable: any): variable is QueryResult {
return variable
&& variable.query
&& isDataQuery(variable.query)
&& variable.result
&& (
typeof variable.result === "object"
&& !Array.isArray(variable.result)
&& variable.result !== null
);
}
export function isDataQuery(variable: any): variable is DataQuery {
+56
View File
@@ -0,0 +1,56 @@
import { Component, createEffect, createSignal, onMount } from "solid-js";
import { ResultKeyVal } from "../../consts";
import { cityCoords } from "./cityCoords";
export const MapView: Component<{ data: () => ResultKeyVal[] }> = ({ data }) => {
const [getCanvas, setCanvas] = createSignal<HTMLCanvasElement>();
const [getImg, setImg] = createSignal(new Image());
let lastCoords: [string, {x: number, y: number }, number][] = [];
onMount(() => {
const ctx = getCanvas()!.getContext("2d")!;
const img = new Image();
img.onload = () => {
setImg(img);
drawOnMap(ctx, img, lastCoords);
};
img.src = "src/assets/map_1920x1080.webp";
});
createEffect(() => {
const ctx = getCanvas()!.getContext("2d")!;
const coordsAndData: [string, {x: number, y: number }, number][] = data()
.map(([city, value]) => [
city,
cityCoords[city as keyof typeof cityCoords],
typeof value === "number" ? value : -99,
]);
lastCoords = coordsAndData;
drawOnMap(ctx, getImg(), coordsAndData);
});
return (
<canvas ref={setCanvas} width="1920px" height="1080px" />
);
}
function drawOnMap(
ctx: CanvasRenderingContext2D,
img: HTMLImageElement,
coords: [string, {x: number, y: number }, number][]
): void {
const size = 80;
ctx.font = "bold 30px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.drawImage(img, 0, 0);
coords.forEach(([, {x, y}, value]) => {
ctx.beginPath();
ctx.fillStyle = "#FFFFFF";
ctx.rect(x, y, size, size);
ctx.fill();
ctx.fillStyle = "#000000";
ctx.fillText(`${value}`, x + size/2, y + size/2);
});
}
+35
View File
@@ -0,0 +1,35 @@
export const cityCoords = {
"Ainaži": { x: 685, y: 146 },
"Alūksne": { x: 1176, y: 294 },
"Bauska": { x: 721, y: 608 },
"Dagda": { x: 1280, y: 680 },
"Daugavgrīva": { x: 640, y: 410 },
"Daugavpils": { x: 1140, y: 739 },
"Dobele": { x: 550, y: 580 },
"Gulbene": { x: 1100, y: 370 },
"Jelgava": { x: 620, y: 570 },
"Kalnciems": { x: 600, y: 500 },
"Kolka": { x: 380, y: 200 },
"Kuldīga": { x: 300, y: 470 },
"Lielpēči": { x: 780, y: 490 },
"Liepāja": { x: 124, y: 638 },
"Madona": { x: 1004, y: 432 },
"Mērsrags": { x: 480, y: 300 },
"Pāvilosta": { x: 160, y: 450 },
"Piedruja": { x: 1290, y: 770 },
"Priekuļi": { x: 900, y: 320 },
"Rēzekne": { x: 1229, y: 544 },
"Rīga": { x: 689, y: 430 },
"Rucava": { x: 140, y: 710 },
"Rūjiena": { x: 870, y: 140 },
"Saldus": { x: 383, y: 561 },
"Sigulda": { x: 810, y: 380 },
"Sīļi": { x: 1050, y: 640 },
"Skrīveri": { x: 880, y: 530 },
"Skulte": { x: 710, y: 320 },
"Stende": { x: 402, y: 400 },
"Ventspils": { x: 210, y: 329 },
"Vičaki": { x: 270, y: 260},
"Zīlāni": { x: 983, y: 581 },
"Zosēni": { x: 970, y: 370 },
};
@@ -1,13 +1,12 @@
import moment from "moment";
import { Accessor, Component, createResource, createSignal } from "solid-js";
import "../css/Result.css"
import { apiHost, ResultKeyVal, resultOrder, ResultOrderKeys } from "../consts";
import { SelectOrder } from "./SelectOrder";
import { CityResult } from "./chart/CityResult";
import { isDataQuery } from "./helpers";
import "../../css/Result.css"
import { apiHost } from "../../consts";
import { isQueryResult } from "../helpers";
import { Result } from "./Result";
export const Result: Component<{
export const QueryResult: Component<{
getCities: Accessor<Set<string>>,
getStart: Accessor<Date>,
getEnd: Accessor<Date>,
@@ -29,7 +28,6 @@ export const Result: Component<{
}
const [queryResource] = createResource(getTimestamp, fetchQuery);
const [getOrderKey, setOrderKey] = createSignal<ResultOrderKeys>("A -> Z");
return (
<div>
@@ -39,10 +37,6 @@ export const Result: Component<{
value="Query Data"
onClick={() => setTimestamp(Date.now())}
/>
<div class="resultTitle">
<h3>Result:</h3>
<SelectOrder getter={getOrderKey} setter={setOrderKey} />
</div>
{ queryResource.loading && (
<div>
<span class="spinner"></span>
@@ -55,19 +49,9 @@ export const Result: Component<{
{ queryResource() && queryResource() instanceof Error &&
<div>{ queryResource().message }</div>
}
{ queryResource() && queryResource().result && isDataQuery(queryResource().query) && (
<div class="result-container">
{ Object.entries(queryResource().result)
.map(keyVal => [...keyVal] as ResultKeyVal)
.sort((a, b) => resultOrder[getOrderKey()](a, b))
.map(cityData =>
<CityResult
city={cityData[0]}
query={queryResource().query}
result={cityData[1]}
/>)
}</div>
)}
{ queryResource() && isQueryResult( queryResource() ) &&
<Result result={queryResource} />
}
</div>
);
}
+58
View File
@@ -0,0 +1,58 @@
import { Component, createSignal, Resource } from "solid-js";
import "../../css/Result.css"
import { ResultKeyVal, resultOrder, ResultOrderKeys } from "../../consts";
import { SelectOrder } from "../SelectOrder";
import { CityResult } from "../chart/CityResult";
import { QueryResult } from "../helpers";
import { MapView } from "../map/MapView";
export const Result: Component<{
result: Resource<QueryResult>
}> = ({ result: resultResource }) => {
const [getOrderKey, setOrderKey] = createSignal<ResultOrderKeys>("A -> Z");
type ResultView = "text" | "map";
const [getResultView, setResultView] = createSignal<ResultView>("text");
const cityData = () =>
Object.entries(resultResource()!.result)
.map(keyVal => [...keyVal] as ResultKeyVal)
.sort((a, b) => resultOrder[getOrderKey()](a, b));
return (
<div>
<div class="resultTitle">
<h3>Result:</h3>
<span>
<input
type="button"
class="secondary"
value="text"
onClick={() => setResultView("text")}
/>
&nbsp; | &nbsp;
<input
type="button"
class="secondary"
value="map"
onClick={() => setResultView("map")}
/>
</span>
<span style={{ visibility: getResultView() === "text" ? "visible" : "hidden" }}>
<SelectOrder getter={getOrderKey} setter={setOrderKey} />
</span>
</div>
<div class="result-container">
{ getResultView() === "text"
? cityData().map(([city, data]) =>
<CityResult
city={city}
query={resultResource()!.query}
result={data}
/>)
: <MapView data={cityData} />
}
</div>
</div>
);
}