Skip to content

Commit

Permalink
Feat heat map bugs fixes (#11)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: matewilk <matewilk@gmail.com>
  • Loading branch information
jsbnr and matewilk committed Apr 25, 2024
1 parent 6fb773b commit 1cee863
Show file tree
Hide file tree
Showing 7 changed files with 44 additions and 15 deletions.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Geo Location Map Visualization is a dynamic web application designed to visually
![Visualization Overview with status](./docs/status-screenshot.jpg)
![Visualisation showing Regions only](./docs/screenshot_regions.jpg)
![Visualisation showing regions and markers together](./docs/screenshot_regionsandmarkers.jpg)
![Visualisation showing heatmap onUS region](./docs/heatmap-usa.jpg)

## Features

Expand All @@ -16,7 +17,8 @@ Geo Location Map Visualization is a dynamic web application designed to visually
- **Interactive Map Clusters:** Locations are clustered on the map for a cleaner visual representation. Clicking on a cluster zooms into that area, revealing individual markers. Clusters also provide a summary representation of the underlying marker status.
- **Real-time Data Updates:** The application periodically fetches and updates data, ensuring the information displayed is current and accurate.
- **Flexible Configuration:** Easily configure via configuration options and query values.
- **Country regions:** Display whole countries with threshold colourings.
- **Country Regions:** Display whole countries with threshold colourings.
- **Regional Heatmaps:** Dynamicaly shade regions to generate heat maps based on value.

## Prerequisites

Expand Down Expand Up @@ -104,12 +106,15 @@ More details regarding region setup can be found [here](./visualizations/store-m

#### Precision, prefix and suffix

Its possible to specify the precision of numbers and add prefix/suffix to values. These adjustments can be made to the `icon_label`` and `tooltip_xxx`` fields by providing extra fields:
Its possible to specify the precision of numbers and add prefix/suffix to values. These adjustments can be made to the `icon_label` and `tooltip_xxx` fields by providing extra fields:

- **`_precision`:** Sets the number of decimal places to display. e.g. `select ... 2 as 'icon_label_precision'...`
- **`_prefix`:** Adds a prefix to the value. e.g. `select ... '$' as 'tooltip_sales_prefix' ...`
- **`_suffix`:** Adds a suffix to the value. e.g. `select ... 'rpm' as 'tooltip_thoughput_suffix' ...`

You may also override the default precision (2) of the icon value by setting `value_precision` if you are not providing an icon label.


#### Example Query

Here is a basic example query, that utilises a lookup table (containing storeID/lat/lng/city) to supply the latitude, longitude to the data:
Expand Down
Binary file added docs/heatmap-usa.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 10 additions & 7 deletions visualizations/store-map-viz/components/Markers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const Markers = () => {
const { markersQuery, disableClusterZoom, markerColors, markerAggregation } =
useProps();

const { data: locations } = useNerdGraphQuery(markersQuery);
const { data: locations, lastUpdateStamp } = useNerdGraphQuery(markersQuery);

const { customColors } = useCustomColors(markerColors);
const customColorsRef = useRef(customColors);
Expand All @@ -26,7 +26,7 @@ const Markers = () => {
customColorsRef.current = customColors;
// Update the renderKey when customColors or markerAggregation changes
setRenderKey(Math.random());
}, [customColors, markerAggregation]);
}, [customColors, markerAggregation,lastUpdateStamp]);

// This is a hack to force a re-render when markers show up for the first time.
const [renderKey, setRenderKey] = useState(Math.random());
Expand All @@ -52,7 +52,7 @@ const Markers = () => {

return (
<MarkerClusterGroup
key={`${markerAggregation}`}
key={`${markerAggregation}-${lastUpdateStamp}`}
singleMarkerMode={true}
spiderfyOnMaxZoom={7}
disableClusteringAtZoom={
Expand All @@ -69,9 +69,12 @@ const Markers = () => {
}}
polygonOptions={getPoligonOptions()}
>
{locations.map((location) => (
<Marker
key={location.storeNumber}
{locations.map((location,idx) => {
if(isNaN(location?.latitude) || isNaN(location?.longitude)) {
return null;
}
return (<Marker
key={`${idx}-${location.value}-${lastUpdateStamp}`}
position={[location.latitude, location.longitude]}
icon={createCustomIcon(location, customColors)}
onClick={() => {
Expand All @@ -82,7 +85,7 @@ const Markers = () => {
>
<LocationPopup location={location} config={tooltipConfig} />
</Marker>
))}
);})}
</MarkerClusterGroup>
);
};
Expand Down
4 changes: 3 additions & 1 deletion visualizations/store-map-viz/hooks/useNerdGraphQuery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const useNerdGraphQuery = (query: string) => {
} = useProps();
const [data, setData] = useState([]);
const [error, setError] = useState(null);
const [lastUpdateStamp, setLastUpdateStamp] = useState(0);

useEffect(() => {
if (!query || query === null || query === undefined) {
Expand All @@ -38,6 +39,7 @@ export const useNerdGraphQuery = (query: string) => {
formatValues(location);
});
setData(results);
setLastUpdateStamp(Date.now());
}
} catch (error) {
console.error("Error fetching data:", error);
Expand All @@ -60,5 +62,5 @@ export const useNerdGraphQuery = (query: string) => {
return () => clearInterval(intervalId);
}, [query, accountId, timeRange, fetchInterval, ignorePicker, defaultSince]);

return { data, error };
return { data, error, lastUpdateStamp };
};
10 changes: 6 additions & 4 deletions visualizations/store-map-viz/queries/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { timeRangeToNrql } from "../utils";

const nrqlQuery = (query, timeRange, defaultSince, ignorePicker) => {
if (ignorePicker === true) {
return query.replace(/(\r\n|\n|\r)/gm, " ") + defaultSince;
return (
query.replace(/(\r\n|\n|\r)/gm, " ").replace("\\", "\\\\") + defaultSince

Check failure

Code scanning / CodeQL

Incomplete string escaping or encoding High

This replaces only the first occurrence of "\".
);
} else {
// Generate the time range part of the NRQL query
const timeRangePart = timeRangeToNrql(timeRange);
// Construct the full NRQL query, remove line breaks
let q = `${query.replace(/(\r\n|\n|\r)/gm, " ")} ${
let q = `${query.replace(/(\r\n|\n|\r)/gm, " ").replace("\\", "\\\\")} ${

Check failure

Code scanning / CodeQL

Incomplete string escaping or encoding High

This replaces only the first occurrence of "\".
timeRangePart === "" ? defaultSince || "" : timeRangePart
}`;
return q;
Expand All @@ -19,15 +21,15 @@ const gqlQuery = (query, timeRange, defaultSince, ignorePicker) => {
query,
timeRange,
defaultSince,
ignorePicker,
ignorePicker
)}" ) { results }`;
};

export const nerdGraphQuery = (
query,
timeRange,
defaultSince,
ignorePicker,
ignorePicker
) => {
return `
query($id: Int!) {
Expand Down
2 changes: 1 addition & 1 deletion visualizations/store-map-viz/utils/customIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export const createCustomIcon = (location, customColors) => {
if (location.icon_label !== undefined) {
markerLabel = location.icon_label;
} else if (location.value !== undefined) {
markerLabel = location.value;
markerLabel = location.formatted_value;
}

return L.divIcon({
Expand Down
17 changes: 17 additions & 0 deletions visualizations/store-map-viz/utils/dataFormatting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,23 @@ export const formatValues = (location) => {
const label_suffix = location.icon_label_suffix;
const label_precision = location.icon_label_precision;

//Format the icon value so it fits in the icon nicely
location.formatted_value=location.value;
if(location.value_precision !==undefined) {
//precision for value was supplied in query
location.formatted_value = location.value.toFixed(parseInt(location.value_precision))
} else {
//precision of value needs to be determined
if(!isNaN(location.value)) {
//the value is a number (we dont try and fix its decimal places if its not)
// We need to round to something sensible only if it has decimals already
let isDecimal = (location.value - Math.floor(location.value)) !== 0;
if(isDecimal) {
location.formatted_value = location.value.toFixed(2);
}
}
}

Object.keys(location).forEach((key) => {
const isClusterData = key.includes("cluster_");
const isTooltip = key.includes("tooltip_");
Expand Down

0 comments on commit 1cee863

Please sign in to comment.