Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle date bounded variables #150

Merged
merged 41 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
979755b
add a dictionary for present/absent years for each var
edwardchalstrey1 May 20, 2024
4533612
attempt to make the prev commit more efficient
edwardchalstrey1 May 20, 2024
92871cc
colour setting for variables in world map works as expected for all s…
edwardchalstrey1 May 20, 2024
2e339b0
remove console logs
edwardchalstrey1 May 20, 2024
781831e
Add present/absent years to tooltip
edwardchalstrey1 May 20, 2024
50ec3e6
filter by polity.id properly
edwardchalstrey1 May 20, 2024
fe1fd51
remove if len(variable_dict) > 1:
edwardchalstrey1 May 20, 2024
ec50ae1
got the correct vars (printing)
edwardchalstrey1 May 20, 2024
82bfe5d
get the correct info in correct dict structure
edwardchalstrey1 May 20, 2024
1003696
add variable_obj_2 as the saved dict
edwardchalstrey1 May 20, 2024
fecbfc2
set colour using shape[variable + '_dict']
edwardchalstrey1 May 20, 2024
0e9448a
update tooltip
edwardchalstrey1 May 20, 2024
f4f78a8
add years to absent/present selection
edwardchalstrey1 May 21, 2024
0b6f027
handle dicts with strings correctly
edwardchalstrey1 May 21, 2024
2ab9b41
temp uncomment RT
edwardchalstrey1 May 21, 2024
7447554
handle RT
edwardchalstrey1 May 21, 2024
7369c47
restrict RT again
edwardchalstrey1 May 21, 2024
637180e
ensure RT uses _dict
edwardchalstrey1 May 21, 2024
1dc80a2
rename vars for clarity
edwardchalstrey1 May 21, 2024
88fa117
update tests to include years for absent/present vars in test_assign_…
edwardchalstrey1 May 21, 2024
537e47d
remove comment
edwardchalstrey1 May 21, 2024
928c144
remove comment
edwardchalstrey1 May 21, 2024
ecf41f1
add dictionaries with years for language vars
edwardchalstrey1 May 21, 2024
2d4dfbd
restore language test
edwardchalstrey1 May 21, 2024
7063699
use categorical var dicts properly, but hitting errors
edwardchalstrey1 May 21, 2024
0d16e17
remove if statement not needed
edwardchalstrey1 May 21, 2024
c258e33
use the non-dict categorical vars for uncoded etc
edwardchalstrey1 May 21, 2024
b969c29
show all absent/present options for a shape
edwardchalstrey1 May 21, 2024
fe85f0e
list vars without years as uncoded
edwardchalstrey1 May 21, 2024
2620725
absent from and to
edwardchalstrey1 May 21, 2024
10a7968
update comment
edwardchalstrey1 May 22, 2024
5cc643e
add comment
edwardchalstrey1 May 22, 2024
501f721
update comment
edwardchalstrey1 May 22, 2024
869df5a
update comment
edwardchalstrey1 May 22, 2024
6e75310
make legend consistent with tooltip on capitalisation
edwardchalstrey1 May 22, 2024
e028811
rename A~P to Absent then Present
edwardchalstrey1 May 22, 2024
cf603f4
consistent capitalisation
edwardchalstrey1 May 22, 2024
cc5a6b8
year uncoded
edwardchalstrey1 May 22, 2024
09a6c6c
use common func longAbsentPresentVarName
edwardchalstrey1 May 22, 2024
1bd5f1c
add comment todo
edwardchalstrey1 May 22, 2024
deeff3f
update comment
edwardchalstrey1 May 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions seshat/apps/core/static/core/js/map_functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -271,15 +271,7 @@ function updateLegend() {
colorBox.style.border = '1px solid black';
}

if (key === 'A~P') {
legendItem.appendChild(document.createTextNode('Absent then present'));
} else if (key === 'P~A') {
legendItem.appendChild(document.createTextNode('Present then absent'));
} else if (key === 'unknown') {
legendItem.appendChild(document.createTextNode('Coded unknown'));
} else {
legendItem.appendChild(document.createTextNode(`${key[0].toUpperCase()}${key.slice(1)}`));
}
legendItem.appendChild(document.createTextNode(longAbsentPresentVarName(key)));

legendDiv.appendChild(legendItem);
}
Expand Down Expand Up @@ -330,4 +322,19 @@ function updateCategoricalVariableSelection(variable){
var varSelectElement = document.getElementById('chooseVariable');
var varText = varSelectElement.options[varSelectElement.selectedIndex].text;
document.querySelector('label[for="chooseCategoricalVariableSelection"]').textContent = varText + ': ';
}

function longAbsentPresentVarName(var_name){
if (var_name === 'A~P') {
var_name = 'Absent then Present';
} else if (var_name === 'P~A') {
var_name = 'Present then Absent';
} else if (var_name === 'unknown') {
var_name = 'Coded Unknown';
} else if (var_name === 'no seshat page') {
var_name = 'No Seshat Page';
} else {
var_name = `${var_name[0].toUpperCase()}${var_name.slice(1)}`;
}
return var_name;
}
114 changes: 100 additions & 14 deletions seshat/apps/core/templates/core/world_map.html
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ <h3>Base Map</h3>
];

// Iterate through shapesData and ensure that fields that are lists are interpreted properly
// TODO: find a way to avoid having to do this
shapesData.forEach(function (shape) {
if (shape.language) {
shape.language = JSON.parse(shape.language.replace(/'/g, '"'));
Expand All @@ -241,6 +242,13 @@ <h3>Base Map</h3>
if (shape.linguistic_family) {
shape.linguistic_family = JSON.parse(shape.linguistic_family.replace(/'/g, '"'));
}
// Any fields that end _dict should be converted to a dictionary from a string
for (const [key, value] of Object.entries(shape)) {
if (key.endsWith('_dict')) {
let newValue = value.replace(/None/g, 'null');
shape[key] = JSON.parse(newValue.replace(/'/g, '"'));
}
}
});

// Load seshat_id_page_id dictionary
Expand Down Expand Up @@ -428,26 +436,67 @@ <h3>Base Map</h3>
};
} else if (variable in categorical_variables){
shapeWeight = 2;
if (shape[variable].includes(document.getElementById('chooseCategoricalVariableSelection').value)) {
if (shape[variable].length > 1) {
shapeColour = oneLanguageColourMapping['Present'];
} else {
shapeColour = oneLanguageColourMapping['Present exclusively'];
// If the shape has a dictionary for the variable the key of the dict is the variable value and the value is a list of start and end years.
// Iterate through it and choose the colour based on the number of that variable that are active in the selected year
var numberOfThisVariable = 0; // Number of different values of this variable that are active in the selected year
var selectedValueOfThisVariablePresent = false; // Whether the selected value of this variable is active in the selected year
// Iterate through key/values in the dictionary
for (const [key, value] of Object.entries(shape[variable + '_dict'])) {
let startYear = value[0];
let endYear = value[1];
// If startYear or endYear are None, use the polity start_year or end_year
// ensuring that the shape is coloured by the variable selection for all years
if (startYear == 'None' || startYear == null) {
startYear = shape.polity_start_year;
}
if (endYear == 'None' || endYear == null) {
endYear = shape.polity_end_year;
}
if (parseInt(startYear) <= selectedYearInt && parseInt(endYear) >= selectedYearInt) {
numberOfThisVariable++;
if (key.includes(document.getElementById('chooseCategoricalVariableSelection').value)) {
selectedValueOfThisVariablePresent = true;
}
}
}
if (numberOfThisVariable > 1 && selectedValueOfThisVariablePresent) {
shapeColour = oneLanguageColourMapping['Present'];
} else if (numberOfThisVariable == 1 && selectedValueOfThisVariablePresent) {
shapeColour = oneLanguageColourMapping['Present exclusively'];
} else if (numberOfThisVariable > 0 && !selectedValueOfThisVariablePresent) {
shapeColour = oneLanguageColourMapping['Absent'];
} else {
if (shape[variable][0] == 'Unknown') { // If the categorical var is unknown or uncoded, there will be only one in the list
shapeColour = oneLanguageColourMapping['Unknown'];
} else if (shape[variable][0] == 'Uncoded') {
shapeColour = oneLanguageColourMapping['Uncoded'];
} else if (shape[variable][0] == 'No Seshat page') {
shapeColour = oneLanguageColourMapping['No Seshat page'];
} else {
shapeColour = oneLanguageColourMapping['Absent'];
}
}
}

} else { // Absent-present variables
shapeWeight = 2;
shapeColour = variableColourMapping[shape[variable]];
if (shape[variable + '_dict']) {
// Iterate through key/values in the dictionary
for (const [key, value] of Object.entries(shape[variable + '_dict'])) {
let startYear = value[0];
let endYear = value[1];
// If startYear or endYear are None, use the polity start_year or end_year
// ensuring that the shape is coloured by the variable selection for all years
if (startYear == 'None' || startYear == null) {
startYear = shape.polity_start_year;
}
if (endYear == 'None' || endYear == null) {
endYear = shape.polity_end_year;
}
if (parseInt(startYear) <= selectedYearInt && parseInt(endYear) >= selectedYearInt) {
shapeColour = variableColourMapping[key];
break; // Only one of present, absent, P~A, A~P, unknown, uncoded should be active at a time, so break after finding the active one
}
}
}
}

// If the shape spans the selected year
Expand Down Expand Up @@ -537,15 +586,52 @@ <h3>Base Map</h3>
<td>${formattedArea} Km<sup>2</sup></td>
</tr>
`;
// Absent/present variables
if (variable != 'polity' && !categorical_variables.hasOwnProperty(variable)) {
popupContent = popupContent + `
<tr>
<td><span style="text-transform: capitalize;">${variable}</span></td>
<td><span style="text-transform: capitalize;">${shape[variable]}</span></td>
</tr>
`;
if (shape[variable + '_dict']) {
// Iterate through key/values in the dictionary
var counter = 0;
for (const [key, value] of Object.entries(shape[variable + '_dict'])) {
let startYear = value[0];
let endYear = value[1];
let variable_value = longAbsentPresentVarName(key);
// If startYear or endYear are None, state these as uncoded
if (startYear == 'None' || startYear == null) {
startYear = '[Year Uncoded]'
}
if (endYear == 'None' || endYear == null) {
endYear = '[Year Uncoded]'
}
if (counter == 0) {
popupContent = popupContent + `
<tr>
<td>${variable}</td>
<td>${variable_value} from ${startYear} to ${endYear}</td>
</tr>
`;
} else {
popupContent = popupContent + `
<tr>
<td></td>
<td>${variable_value} from ${startYear} to ${endYear}</td>
</tr>
`;
}
counter++;
}
} else {
popupContent = popupContent + `
<tr>
<td><span style="text-transform: capitalize;">${variable}</span></td>
<td><span style="text-transform: capitalize;">${shape[variable]}</span></td>
</tr>
`;
}
}
if (variable == 'language' || variable == 'linguistic_family' || variable == 'language_genus') {
// TODO: if these variables are populated start to get entries for years,
// this will need to be updated to use shape[variable + '_dict'] as done above for absent/present variables
// Note: the actual colouring of the shapes is done in the style function above and this is already set up to handle the dictionaries
popupContent = popupContent + `
<tr>
<td>Languages</td>
Expand Down
16 changes: 14 additions & 2 deletions seshat/apps/core/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,21 +125,29 @@ def setUp(self):
Judge.objects.create(
name='judge',
judge='present',
year_from=2003,
year_to=2004,
polity_id=2
)
Gov_res_pub_pros.objects.create(
name='gov_res_pub_pros',
coded_value='absent',
year_from=2002,
year_to=2003,
polity_id=2
)
Polity_language.objects.create(
name='language',
language='English',
polity_id=2
polity_id=2,
year_from=1998,
year_to=2000
)
Polity_language.objects.create(
name='language',
language='French',
year_from=1999,
year_to=2007,
polity_id=2
)

Expand Down Expand Up @@ -440,7 +448,9 @@ def test_assign_variables_to_shapes(self):
self.assertEqual(result_variables['Religion Tolerance']['gov_res_pub_pros'], expected_result_variables_gov_res_pub_pros)
# Test that the shapes have been updated with the variables
self.assertEqual(result_shapes[0]['Judge'], 'present')
self.assertEqual(result_shapes[0]['Judge_dict'], {'present': [2003, 2004]})
self.assertEqual(result_shapes[0]['Government Restrictions on Public Proselytizings'], 'absent')
self.assertEqual(result_shapes[0]['Government Restrictions on Public Proselytizings_dict'], {'absent': [2002, 2003]})

def test_assign_categorical_variables_to_shapes(self):
"""Test the assign_categorical_variables_to_shapes function."""
Expand All @@ -464,4 +474,6 @@ def test_assign_categorical_variables_to_shapes(self):
'full_name': 'Language'
}
self.assertEqual(result_variables['General Variables']['polity_language'], expected_result_variables_language)
self.assertEqual(result_shapes[0]['language'], ['English', 'French'])
self.assertEqual(result_shapes[0]['language'], ['English', 'French'])
self.assertEqual(result_shapes[0]['language_dict']['English'], [1998, 2000])
self.assertEqual(result_shapes[0]['language_dict']['French'], [1999, 2007])
31 changes: 28 additions & 3 deletions seshat/apps/core/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import sys
import importlib

from collections import defaultdict
from seshat.utils.utils import adder, dic_of_all_vars, list_of_all_Polities, dic_of_all_vars_in_sections

from django.contrib.sites.shortcuts import get_current_site
Expand Down Expand Up @@ -2834,16 +2835,32 @@ def assign_variables_to_shapes(shapes, app_map):
variable_formatted = variables[app_name_long][variable]['formatted']
variable_objs = {obj.polity_id: obj for obj in class_.objects.filter(polity_id__in=polities.values())}

all_variable_objs = {}
for obj in class_.objects.filter(polity_id__in=polities.values()):
try:
variable_value = getattr(obj, variable)
except AttributeError: # For rt models where coded_value is used
variable_value = getattr(obj, 'coded_value')
if obj.polity_id not in all_variable_objs:
all_variable_objs[obj.polity_id] = {}
all_variable_objs[obj.polity_id][variable_value] = [obj.year_from, obj.year_to]

for shape in shapes:
shape[variable_formatted] = 'uncoded' # Default value
polity = polities.get(shape['seshat_id'])
if polity:
variable_obj = variable_objs.get(polity.id)
try:
variable_obj_dict = all_variable_objs[polity.id]
except KeyError:
pass
if variable_obj:
try:
shape[variable_formatted] = getattr(variable_obj, variable) # absent/present choice
shape[variable_formatted] = getattr(variable_obj, variable) # absent/present choice
shape[variable_formatted + '_dict'] = variable_obj_dict
except AttributeError: # For rt models where coded_value is used
shape[variable_formatted] = getattr(variable_obj, 'coded_value')
shape[variable_formatted + '_dict'] = variable_obj_dict
else:
shape[variable_formatted] = 'no seshat page'

Expand Down Expand Up @@ -2886,8 +2903,11 @@ def assign_categorical_variables_to_shapes(shapes, variables):
# Add language variable info to polity shapes
for shape in shapes:
shape['linguistic_family'] = []
shape['linguistic_family_dict'] = {}
shape['language_genus'] = []
shape['language_genus_dict'] = {}
shape['language'] = []
shape['language_dict'] = {}
if shape['seshat_id'] != 'none': # Skip shapes with no seshat_id
polity = polities.get(shape['seshat_id'])
if polity:
Expand All @@ -2896,6 +2916,11 @@ def assign_categorical_variables_to_shapes(shapes, variables):
shape['language_genus'].extend([lg.language_genus for lg in language_genuses.get(polity.id, [])])
shape['language'].extend([l.language for l in languages.get(polity.id, [])])

# Get the years for the linguistic family, language genus, and language for the polity
shape['linguistic_family_dict'].update({lf.linguistic_family: [lf.year_from, lf.year_to] for lf in linguistic_families.get(polity.id, [])})
shape['language_genus_dict'].update({lg.language_genus: [lg.year_from, lg.year_to] for lg in language_genuses.get(polity.id, [])})
shape['language_dict'].update({l.language: [l.year_from, l.year_to] for l in languages.get(polity.id, [])})

# If no linguistic family, language genus, or language was found, append 'Uncoded'
polity = polities.get(shape['seshat_id'])
if polity:
Expand All @@ -2919,8 +2944,8 @@ def assign_categorical_variables_to_shapes(shapes, variables):
app_map = {
'sc': 'Social Complexity Variables',
'wf': 'Warfare Variables (Military Technologies)',
# 'rt': 'Religion Tolerance', TODO: Temporarily restricted. Uncomment when ready.
# 'general': 'General Variables', TODO: Not implemented yet
# 'rt': 'Religion Tolerance', # TODO: Implemented but temporarily restricted. Uncomment when ready.
# 'general': 'General Variables', # TODO: Partially implmented and hardcoded in assign_categorical_variables_to_shapes.
}

# Get sorted lists of choices for each categorical variable
Expand Down
Loading