Skip to content

Commit 5e40beb

Browse files
committed
Merge branch 'main' into map-pin-closest-city
2 parents ebd37e7 + 281b2fe commit 5e40beb

File tree

4 files changed

+196
-54
lines changed

4 files changed

+196
-54
lines changed

src/components/app.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useEffect } from "react";
2+
import { initializeNeoPlugin } from "../models/plugin-state";
23
import { isNonEmbedded } from "../utils/embed-check";
3-
import { initializeNeoPlugin } from "../utils/codap-utils";
44
import { TabContainer } from "./tabs/tab-container";
55
import { Provider } from "./ui/provider";
66

src/models/data-manager.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
sendMessage,
1111
} from "@concord-consortium/codap-plugin-api";
1212
import { decodePng } from "@concord-consortium/png-codec";
13-
import { kChartGraphName, kDataContextName, kMapPinsCollectionName, kXYGraphName } from "../data/constants";
13+
import { kChartGraphName, kDataContextName, kMapPinsCollectionName, kXYGraphName } from "../data/constants";
1414
import { createOrUpdateGraphs, createOrUpdateDateSlider, createOrUpdateMap, addConnectingLinesToGraph,
1515
addRegionOfInterestToGraphs, updateGraphRegionOfInterest, updateLocationColorMap, rescaleGraph
1616
} from "../utils/codap-utils";
@@ -144,13 +144,16 @@ export class DataManager {
144144
};
145145

146146
pluginState.pins.forEach(pin => {
147-
const color = geoImage.extractColor(pin.lat, pin.long);
148-
const label = pin.label || pinLabel(pin);
149-
const paletteIndex = this.reversePalette?.[GeoImage.rgbToNumber(color)] ?? -1;
147+
const extractedColor = geoImage.extractColor(pin.lat, pin.long);
148+
const label = pinLabel(pin);
149+
const paletteIndex = this.reversePalette?.[GeoImage.rgbToNumber(extractedColor)] ?? -1;
150+
const paletteValue = neoDataset.paletteToValue(paletteIndex);
151+
const color = paletteValue === null ? {r: 148, g: 148, b: 148} : extractedColor;
152+
150153
neoDatasetImage.pins[label] = {
151154
color: GeoImage.rgbToHex(color),
152155
paletteIndex,
153-
value: neoDataset.paletteToValue(paletteIndex),
156+
value: paletteValue,
154157
label,
155158
pinColor: pin.color,
156159
};

src/models/plugin-state.ts

Lines changed: 161 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,70 @@
1-
import { getAllItems, IResult } from "@concord-consortium/codap-plugin-api";
2-
import { makeAutoObservable } from "mobx";
1+
import { addDataContextChangeListener, getAllItems, getCaseByID, getCaseBySearch, getSelectionList, initializePlugin,
2+
IResult, sendMessage } from "@concord-consortium/codap-plugin-api";
3+
import { makeAutoObservable, reaction } from "mobx";
34
import {
4-
kPinColorAttributeName, kPinDataContextName, kPinLatAttributeName, kPinLongAttributeName
5+
kDataContextName,
6+
kInitialDimensions,
7+
kMapPinsCollectionName,
8+
kPinColorAttributeName, kPinDataContextName, kPinLatAttributeName, kPinLongAttributeName,
9+
kPluginName,
10+
kVersion
511
} from "../data/constants";
12+
import { createOrUpdateMap, createSelectionList, deleteSelectionList, updateSelectionList } from "../utils/codap-utils";
613
import { NeoDataset } from "./neo-types";
714
import { getMapComponentInfo } from "../utils/codap-utils";
815
import { geoLocSearch, MapComponentInfo } from "../utils/location-utils";
916

10-
interface IMapPin {
17+
export interface IMapPin {
1118
color: string;
1219
id: string;
1320
lat: number;
1421
long: number;
1522
label: string;
1623
}
1724

25+
export interface ILocationCase {
26+
label: string;
27+
pinColor: string;
28+
}
29+
1830
export function pinLabel(pin: IMapPin) {
1931
return `${pin.lat.toFixed(2)}, ${pin.long.toFixed(2)}`;
2032
}
2133

34+
2235
class PluginState {
2336
neoDataset: NeoDataset | undefined;
2437
neoDatasetName = "";
2538
pins: IMapPin[] = [];
39+
selectedPins: IMapPin[] = [];
40+
selectedCases: any[] =[];
2641

2742
constructor() {
2843
makeAutoObservable(this);
44+
// Reaction to changes in selectedPins from MapPinDataContext
45+
reaction(
46+
() => this.selectedPins,
47+
(selectedPins) => {
48+
this.handleSelectionChange(
49+
selectedPins,
50+
kDataContextName,
51+
kMapPinsCollectionName,
52+
(pin) => `label == ${pinLabel(pin)}`
53+
);
54+
}
55+
);
56+
// Reaction to changes in selectedCases NEOPluginDataContext
57+
reaction(
58+
() => this.selectedCases,
59+
(selectedCases) => {
60+
this.handleSelectionChange(
61+
selectedCases,
62+
kPinDataContextName,
63+
kMapPinsCollectionName,
64+
(sCase) => `pinColor == ${sCase.pinColor}`
65+
);
66+
}
67+
);
2968
}
3069

3170
setNeoDataset(neoDataset: NeoDataset | undefined) {
@@ -61,6 +100,124 @@ class PluginState {
61100
});
62101
}
63102
}
103+
104+
setSelectedPins(selectedPins: IMapPin[]) {
105+
this.selectedPins = selectedPins;
106+
}
107+
108+
setSelectedCases(selectedCases: any[]) {
109+
this.selectedCases = selectedCases;
110+
}
111+
112+
async handleSelectionChange<T>(
113+
selectedItems: T[], dataContextName: string, collectionName: string, searchQueryFn: (item: T) => string
114+
): Promise<void> {
115+
if (selectedItems.length === 0) {
116+
deleteSelectionList(dataContextName);
117+
return;
118+
}
119+
120+
for (const item of selectedItems) {
121+
const searchQuery = searchQueryFn(item);
122+
const result = await getCaseBySearch(dataContextName, collectionName, searchQuery);
123+
124+
if (result.success) {
125+
const selectedItemIds = result.values.map((val: any) => val.id);
126+
if (selectedItems.length === 1) {
127+
createSelectionList(dataContextName, selectedItemIds);
128+
return;
129+
} else {
130+
const updateSelection = await updateSelectionList(dataContextName, selectedItemIds);
131+
if (!updateSelection.success) {
132+
createSelectionList(dataContextName, selectedItemIds);
133+
}
134+
}
135+
}
136+
}
137+
}
138+
}
139+
140+
export async function initializeNeoPlugin() {
141+
initializePlugin({ pluginName: kPluginName, version: kVersion, dimensions: kInitialDimensions });
142+
143+
// Create the pin dataset
144+
await sendMessage("create", `dataContext`, {
145+
name: kPinDataContextName,
146+
collections: [
147+
{
148+
name: "Map Pins",
149+
attrs: [
150+
{ name: kPinLatAttributeName, type: "numeric" },
151+
{ name: kPinLongAttributeName, type: "numeric" },
152+
{ name: kPinColorAttributeName, type: "color" }
153+
]
154+
}
155+
]
156+
});
157+
158+
// Create map if it doesn't exist
159+
await createOrUpdateMap("Map");
160+
161+
// See if there are any existing pins
162+
pluginState.updatePins();
163+
164+
// Set up a listener for changes to the pin dataset
165+
addDataContextChangeListener(kPinDataContextName, notification => {
166+
const { operation } = notification.values;
167+
168+
if (["createCases", "deleteCases", "updateCases"].includes(operation)) {
169+
pluginState.updatePins();
170+
}
171+
});
172+
// Set up a listener for pin selection
173+
addDataContextChangeListener(kPinDataContextName, async notification => {
174+
const { operation, result } = notification.values;
175+
if (operation === "selectCases" && result.success) {
176+
const selectedPins = await getSelectionList(kPinDataContextName);
177+
const selectedPinValues: IMapPin[] = await Promise.all(
178+
selectedPins.values.map(async (pin: any) => {
179+
const pinItem = await getCaseByID(kPinDataContextName, pin.caseID);
180+
if (pinItem.success) {
181+
const pinValues = pinItem.values;
182+
const pinCase = (pinValues as any).case;
183+
return {
184+
id: pinCase.id,
185+
lat: pinCase.values.pinLat,
186+
long: pinCase.values.pinLong,
187+
color: pinCase.values.pinColor,
188+
};
189+
}
190+
return null;
191+
})
192+
);
193+
pluginState.setSelectedPins(selectedPinValues);
194+
}
195+
});
196+
197+
// Set up a listener for case selection
198+
addDataContextChangeListener(kDataContextName, async notification => {
199+
const { operation, result } = notification.values;
200+
if (operation === "selectCases" && result.success) {
201+
const selectedCases = await getSelectionList(kDataContextName);
202+
const selectedPinCases = selectedCases.values
203+
.filter((sCase: any) => sCase.collectionName === kMapPinsCollectionName);
204+
const selectedCaseValues: any[] = await Promise.all(
205+
selectedPinCases.map(async (sCase: any) => {
206+
const caseItem = await getCaseByID(kDataContextName, sCase.caseID);
207+
if (caseItem.success) {
208+
const caseValues = caseItem.values;
209+
return {
210+
id: caseValues.id,
211+
label: caseValues.case.values.label,
212+
pinColor: caseValues.case.values.pinColor,
213+
};
214+
}
215+
return null;
216+
})
217+
);
218+
pluginState.setSelectedCases(selectedCaseValues);
219+
}
220+
});
64221
}
65222

66223
export const pluginState = new PluginState();

src/utils/codap-utils.ts

Lines changed: 26 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,7 @@
1-
import { addDataContextChangeListener, codapInterface, initializePlugin, sendMessage
2-
} from "@concord-consortium/codap-plugin-api";
3-
import {
4-
kPinDataContextName, kPinLatAttributeName, kPinLongAttributeName, kPinColorAttributeName,
5-
kPluginName, kInitialDimensions, kVersion,
6-
kDataContextName, kMapPinsCollectionName, kOneMonthInSeconds,
7-
kMapName, kSliderComponentName, kChartGraphName, kXYGraphName,
8-
} from "../data/constants";
9-
import { pluginState } from "../models/plugin-state";
10-
11-
export async function initializeNeoPlugin() {
12-
initializePlugin({ pluginName: kPluginName, version: kVersion, dimensions: kInitialDimensions });
13-
14-
// Create the pin dataset
15-
await sendMessage("create", `dataContext`, {
16-
name: kPinDataContextName,
17-
collections: [
18-
{
19-
name: "Map Pins",
20-
attrs: [
21-
{ name: kPinLatAttributeName, type: "numeric" },
22-
{ name: kPinLongAttributeName, type: "numeric" },
23-
{ name: kPinColorAttributeName, type: "color" }
24-
]
25-
}
26-
]
27-
});
28-
29-
// Create map if it doesn't exist
30-
await createOrUpdateMap("Map");
31-
32-
// See if there are any existing pins
33-
pluginState.updatePins();
34-
35-
// Set up a listener for changes to the pin dataset
36-
addDataContextChangeListener(kPinDataContextName, notification => {
37-
const { operation } = notification.values;
38-
39-
if (["createCases", "deleteCases", "updateCases"].includes(operation)) {
40-
pluginState.updatePins();
41-
}
42-
});
43-
44-
}
1+
import { codapInterface, sendMessage }
2+
from "@concord-consortium/codap-plugin-api";
3+
import { kOneMonthInSeconds, kMapName, kMapPinsCollectionName, kDataContextName, kSliderComponentName,
4+
kChartGraphName, kXYGraphName } from "../data/constants";
455

466
export async function createOrUpdateMap(title: string, url?: string): Promise<void> {
477
const mapProps: Record<string, any> = {
@@ -210,6 +170,28 @@ export const updateLocationColorMap = async (colorMap: Record<string,string>) =>
210170
return updateColorMap;
211171
};
212172

173+
export const getSelectionList = async (dataContext: string) => {
174+
const selectionList = await sendMessage("get", `dataContext[${dataContext}].selectionList`);
175+
if (selectionList.success) {
176+
return selectionList.values;
177+
} else {
178+
console.error("Error getting selection list");
179+
return [];
180+
}
181+
};
182+
183+
export const deleteSelectionList = async (dataContext: string) => {
184+
await sendMessage("create", `dataContext[${dataContext}].selectionList`, []);
185+
};
186+
187+
export const createSelectionList = async (dataContext: string, selectedCaseIds: string[]) => {
188+
await sendMessage("create", `dataContext[${dataContext}].selectionList`, selectedCaseIds);
189+
};
190+
191+
export const updateSelectionList = async (dataContext: string, selectedCaseIds: string[]) => {
192+
const result = await sendMessage("update", `dataContext[${dataContext}].selectionList`, selectedCaseIds);
193+
return result;
194+
};
213195
export const getMapComponentInfo = async () => {
214196
const mapInfo = await sendMessage("get", `component[${kMapName}]`);
215197
if (mapInfo.success) {

0 commit comments

Comments
 (0)