Skip to content

Commit

Permalink
Encode rowValue of a componentList when it contains variables (#5723)
Browse files Browse the repository at this point in the history
* Encode rowValue of a componentList when it contain variables

* Update jaspTestModule
  • Loading branch information
boutinb authored Oct 29, 2024
1 parent 00c07fa commit f86cf17
Show file tree
Hide file tree
Showing 13 changed files with 122 additions and 69 deletions.
2 changes: 1 addition & 1 deletion Modules/jaspTestModule
15 changes: 9 additions & 6 deletions QMLComponents/analysisbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,25 +149,28 @@ Json::Value& AnalysisBase::_getParentBoundValue(const QVector<JASPControl::Paren
{
Json::Value* parentBoundValues = &(*parentBoundValue)[parent.name];
if (parentBoundValues->isObject() && parentBoundValues->isMember("value") && parentBoundValues->isMember("types"))
parentBoundValues = &(*parentBoundValues)["value"];
parentBoundValues = &(*parentBoundValues)["value"]; // If the control contain variable names, the option values are inside the 'value' member

if (!parentBoundValues->isNull() && parentBoundValues->isArray())
{
for (Json::Value & boundValue : (*parentBoundValues))
{
if (boundValue.isMember(parent.key))
{
Json::Value &val = boundValue[parent.key];
Json::Value* keyValue = &(boundValue[parent.key]);
if (keyValue->isObject() && keyValue->isMember("value") && keyValue->isMember("types"))
keyValue = &((*keyValue)["value"]); // The key can be also a variable name

// The value can be a string or an array of strings (for interaction terms)
if (val.isString() && parent.value.size() == 1)
if (keyValue->isString() && parent.value.size() == 1)
{
if (val.asString() == parent.value[0]) found = true;
if (keyValue->asString() == parent.value[0]) found = true;
}
else if (val.isArray() && val.size() == parent.value.size())
else if (keyValue->isArray() && keyValue->size() == parent.value.size())
{
found = true;
size_t i = 0;
for (const Json::Value& compVal : val)
for (const Json::Value& compVal : *keyValue)
{
if (!compVal.isString() || compVal.asString() != parent.value[i]) found = false;
i++;
Expand Down
42 changes: 31 additions & 11 deletions QMLComponents/boundcontrols/boundcontrolbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,28 +150,48 @@ void BoundControlBase::_readTableValue(const Json::Value &value, const std::stri
}
}

Json::Value BoundControlBase::_getTableValueOption(const Terms& terms, const ListModel::RowControlsValues& componentValuesMap, const std::string& key, bool hasMultipleTerms)
Json::Value BoundControlBase::_getTableValueOption(const Terms& terms, const ListModel::RowControlsValues& componentValuesMap, const std::string& key, bool hasInteraction, bool keyHasVariables)
{
Json::Value result(Json::arrayValue);

for (const Term& term : terms)
{
QMap<QString, Json::Value> componentValues = componentValuesMap[term.asQString()];

Json::Value rowValues(Json::objectValue);
if (hasMultipleTerms)
Json::Value rowValues(Json::objectValue),
keyValue; // depending of keyHasVariables & hasInteraction, keyValue can be an object, an array or a string

if (keyHasVariables)
{
Json::Value keyValue(Json::arrayValue);
for (const std::string& comp : term.scomponents())
keyValue.append(comp);
rowValues[key] = keyValue;
// If the key of the row contains variables, set 'value' and 'types'
Json::Value jsonValue, jsonTypes;

if (hasInteraction)
{
for (const std::string& comp : term.scomponents())
jsonValue.append(comp);
for (columnType type : term.types())
jsonTypes.append(columnTypeToString(type));
}
else
{
jsonValue = term.asString();
jsonTypes = columnTypeToString(term.type());
}
keyValue["value"] = jsonValue;
keyValue["types"] = jsonTypes;
}
else
{
Json::Value keyValue(term.asString());
rowValues[key] = keyValue;
if (hasInteraction)
for (const std::string& comp : term.scomponents())
keyValue.append(comp);
else
keyValue = term.asString();
}

rowValues[key] = keyValue;

QMapIterator<QString, Json::Value> it2(componentValues);
while (it2.hasNext())
{
Expand All @@ -184,9 +204,9 @@ Json::Value BoundControlBase::_getTableValueOption(const Terms& terms, const Lis
return result;
}

void BoundControlBase::_setTableValue(const Terms& terms, const ListModel::RowControlsValues& componentValuesMap, const std::string& key, bool hasMultipleTerms)
void BoundControlBase::_setTableValue(const Terms& terms, const ListModel::RowControlsValues& componentValuesMap, const std::string& key, bool hasInteraction, bool keyHasVariables)
{
setBoundValue(_getTableValueOption(terms, componentValuesMap, key, hasMultipleTerms));
setBoundValue(_getTableValueOption(terms, componentValuesMap, key, hasInteraction, keyHasVariables));
}

bool BoundControlBase::_isValueWithTypes(const Json::Value &value) const
Expand Down
4 changes: 2 additions & 2 deletions QMLComponents/boundcontrols/boundcontrolbase.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ class BoundControlBase : public BoundControl
std::string getName() const;
void handleComputedColumn(const Json::Value& value);

Json::Value _getTableValueOption(const Terms& terms, const ListModel::RowControlsValues& componentValuesMap, const std::string& key, bool hasMultipleTerms);
void _setTableValue(const Terms& terms, const ListModel::RowControlsValues& componentValuesMap, const std::string& key, bool hasMultipleTerms);
Json::Value _getTableValueOption(const Terms& terms, const ListModel::RowControlsValues& componentValuesMap, const std::string& key, bool hasInteraction, bool keyHasVariables);
void _setTableValue(const Terms& terms, const ListModel::RowControlsValues& componentValuesMap, const std::string& key, bool hasInteraction, bool keyHasVariables = false);

void _readTableValue(const Json::Value& value, const std::string& key, bool hasMultipleTerms, Terms& terms, ListModel::RowControlsValues& allControlValues);
bool _isValueWithTypes(const Json::Value &value) const;
Expand Down
12 changes: 11 additions & 1 deletion QMLComponents/boundcontrols/boundcontrolterms.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,17 @@ void BoundControlTerms::bindTo(const Json::Value &value)
for (Term& term : terms)
{
if (typesPart.size() > i) // If the type is given, use it
term.setType(columnTypeFromString(typesPart[i].asString(), columnType::unknown));
{
if (typesPart[i].isArray())
{
columnTypeVec types;
for (const Json::Value& jsonType : typesPart[i])
types.push_back(columnTypeFromString(jsonType.asString(), columnType::unknown));
term.setTypes(types);
}
else
term.setType(columnTypeFromString(typesPart[i].asString(), columnType::unknown));
}
else
{
if (term.type() == columnType::unknown)
Expand Down
4 changes: 2 additions & 2 deletions QMLComponents/controls/componentslistbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ void ComponentsListBase::termsChangedHandler()
{
JASPListControl::termsChangedHandler();

_setTableValue(_termsModel->terms(), _termsModel->getTermsWithComponentValues(), fq(_optionKey), containsInteractions());
_setTableValue(_termsModel->terms(), _termsModel->getTermsWithComponentValues(), fq(_optionKey), containsInteractions(), containsVariables());
bindOffsets();
emit controlNameXOffsetMapChanged();
}
Expand Down Expand Up @@ -308,7 +308,7 @@ Json::Value ComponentsListBase::getJsonFromComponentValues(const ListModel::RowC
for (const QString& term : termsWithComponentValues.keys())
terms.add(Term::readTerm(term));

return _getTableValueOption(terms, termsWithComponentValues, fq(_optionKey), containsInteractions());
return _getTableValueOption(terms, termsWithComponentValues, fq(_optionKey), containsInteractions(), containsVariables());
}

void ComponentsListBase::addItemHandler()
Expand Down
11 changes: 2 additions & 9 deletions QMLComponents/controls/jasplistcontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,13 +299,6 @@ std::vector<std::string> JASPListControl::usedVariables() const
Json::Value JASPListControl::valueTypes() const
{
Json::Value types(Json::arrayValue);
strstrmap variableTypeMap;

// An interaction term has components that can be variables: if the model contains also such variables, the interaction term should get the same types.
// So first check which terms have only 1 component: these terms might be variable names, so keep in a map their types. Use then this map to set the type for interaction terms.
for (const Term& term : model()->terms())
if (term.components().size() == 1)
variableTypeMap[term.asString()] = columnTypeToString(term.type());

for (const Term& term : model()->terms())
{
Expand All @@ -314,8 +307,8 @@ Json::Value JASPListControl::valueTypes() const
else
{
Json::Value compTypes(Json::arrayValue);
for (const std::string& component : term.scomponents())
compTypes.append(variableTypeMap.count(component) > 0 ? variableTypeMap[component] : columnTypeToString(columnType::unknown));
for (columnType type : term.types())
compTypes.append(columnTypeToString(type));
types.append(compTypes);
}
}
Expand Down
42 changes: 29 additions & 13 deletions QMLComponents/models/listmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,9 @@ void ListModel::setVariableType(int ind, columnType type)
if (term.type() == type)
return;

term.setType(type);

emit dataChanged(index(ind, 0), index(ind, 0));
emit columnTypeChanged(term);
Term newTerm(term);
newTerm.setType(type);
sourceColumnTypeChanged(newTerm);
}

columnType ListModel::getVariableType(const QString& name) const
Expand Down Expand Up @@ -613,20 +612,37 @@ void ListModel::sourceNamesChanged(QMap<QString, QString> map)
emit namesChanged(changedNamesMap);
}

int ListModel::sourceColumnTypeChanged(Term sourceTerm)
bool ListModel::sourceColumnTypeChanged(Term sourceTerm)
{
int i = _terms.indexOf(sourceTerm);
if (i >= 0)
bool change = false;
for (int i = 0; i < _terms.size(); i++)
{
Term& term = _terms.at(i);
term.setType(sourceTerm.type());
QModelIndex ind = index(i, 0);
if (term.containsAll(sourceTerm))
{
// A term may have several components: if all sourceTerm's components are in the term's components,
// then for each component in term that is in sourceTerm, replace its type (in term) by the one in sourceTerm
columnTypeVec types = term.types(),
sourceTypes = sourceTerm.types();
int sourceInd = 0;

emit dataChanged(ind, ind, {ListModel::ColumnTypeRole, ListModel::ColumnTypeIconRole, ListModel::ColumnTypeDisabledIconRole});
emit columnTypeChanged(term);
}
for (const QString& sourceComponent : sourceTerm.components())
{
int ind = term.components().indexOf(sourceComponent);
if (ind >= 0 && types.size() > ind && sourceTypes.size() > sourceInd)
types[ind] = sourceTypes[sourceInd];
sourceInd++;
}
term.setTypes(types);
QModelIndex ind = index(i, 0);

emit dataChanged(ind, ind, {ListModel::ColumnTypeRole, ListModel::ColumnTypeIconRole, ListModel::ColumnTypeDisabledIconRole});
emit columnTypeChanged(term);

return i;
change = true;
}
}
return change;
}

bool ListModel::sourceLabelsChanged(QString columnName, QMap<QString, QString> changedLabels)
Expand Down
2 changes: 1 addition & 1 deletion QMLComponents/models/listmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class ListModel : public QAbstractTableModel, public VariableInfoConsumer
public slots:
virtual void sourceTermsReset();
virtual void sourceNamesChanged(QMap<QString, QString> map);
virtual int sourceColumnTypeChanged(Term sourceTerm);
virtual bool sourceColumnTypeChanged(Term sourceTerm);
virtual bool sourceLabelsChanged(QString columnName, QMap<QString, QString> changedLabels = {});
virtual bool sourceLabelsReordered(QString columnName);
virtual void sourceColumnsChanged(QStringList columns);
Expand Down
8 changes: 4 additions & 4 deletions QMLComponents/models/listmodelavailableinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,14 +149,14 @@ void ListModelAvailableInterface::sourceColumnsChanged(QStringList columns)
emit columnsChanged(changedColumns);
}

int ListModelAvailableInterface::sourceColumnTypeChanged(Term term)
bool ListModelAvailableInterface::sourceColumnTypeChanged(Term term)
{
int index = ListModelDraggable::sourceColumnTypeChanged(term);
bool change = ListModelDraggable::sourceColumnTypeChanged(term);

if (index == -1 && _allTerms.contains(term))
if (!change && _allTerms.contains(term))
emit columnTypeChanged(term);

return index;
return change;
}

bool ListModelAvailableInterface::sourceLabelsChanged(QString columnName, QMap<QString, QString> changedLabels)
Expand Down
2 changes: 1 addition & 1 deletion QMLComponents/models/listmodelavailableinterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public slots:
void sourceTermsReset() override;
void sourceNamesChanged(QMap<QString, QString> map) override;
void sourceColumnsChanged(QStringList columns) override;
int sourceColumnTypeChanged(Term name) override;
bool sourceColumnTypeChanged(Term name) override;
bool sourceLabelsChanged(QString columnName, QMap<QString, QString> = {}) override;
bool sourceLabelsReordered(QString columnName) override;
void removeAssignedModel(ListModelAssignedInterface *assignedModel);
Expand Down
9 changes: 6 additions & 3 deletions QMLComponents/models/term.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,11 @@ class Term
bool isDraggable() const { return _draggable; }
void setDraggable(bool draggable) { _draggable = draggable; }

columnType type() const { return _type; }
void setType(columnType type) { _type = type; }
// If a term has several components, its type self is unknown, but the components have maybe a type.
columnType type() const { return _types.size() == 1 ? _types[0] : columnType::unknown; }
void setType(columnType type) { _types = {type}; }
columnTypeVec types() const { return _types; }
void setTypes(columnTypeVec types) { _types = types; }

typedef QStringList::const_iterator const_iterator;
typedef QStringList::iterator iterator;
Expand Down Expand Up @@ -81,7 +84,7 @@ class Term
QStringList _components;
QString _asQString;
bool _draggable = true;
columnType _type = columnType::unknown;
columnTypeVec _types = {columnType::unknown};
};

#endif // TERM_H
38 changes: 23 additions & 15 deletions QMLComponents/models/terms.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ void Terms::add(const Term &term, bool isUnique)
else if (result == 0)
{
itr->setDraggable(term.isDraggable());
itr->setType(term.type());
itr->setTypes(term.types());
}
else if (result < 0)
_terms.push_back(term);
Expand All @@ -165,7 +165,7 @@ void Terms::add(const Term &term, bool isUnique)
else
{
_terms.at(i).setDraggable(term.isDraggable());
_terms.at(i).setType(term.type());
_terms.at(i).setTypes(term.types());
}
}
}
Expand Down Expand Up @@ -348,19 +348,21 @@ Terms Terms::crossCombinations() const
do {

vector<string> combination;
columnTypeVec types;

for (uint i = 0; i < _terms.size(); i++) {
if (!v[i])
{
vector<string> components = _terms.at(i).scomponents();
columnTypeVec termTypes = _terms.at(i).types();
combination.insert(combination.end(), components.begin(), components.end());
types.insert(types.end(), termTypes.begin(), termTypes.end());
}
}

if (combination.size() == 1)
t.add(at(indexOf(combination[0])));
else
t.add(Term(combination));
Term newTerm(combination);
newTerm.setTypes(types);
t.add(newTerm);

} while (std::next_permutation(v.begin(), v.end()));
}
Expand All @@ -380,19 +382,21 @@ Terms Terms::wayCombinations(int ways) const
do {

vector<string> combination;
columnTypeVec types;

for (uint i = 0; i < _terms.size(); ++i) {
if (!v[i])
{
vector<string> components = _terms.at(i).scomponents();
columnTypeVec termTypes = _terms.at(i).types();
combination.insert(combination.end(), components.begin(), components.end());
types.insert(types.end(), termTypes.begin(), termTypes.end());
}
}

if (combination.size() == 1)
t.add(at(indexOf(combination[0])));
else
t.add(Term(combination));
Term newTerm(combination);
newTerm.setTypes(types);
t.add(newTerm);

} while (std::next_permutation(v.begin(), v.end()));
}
Expand All @@ -415,11 +419,15 @@ Terms Terms::ffCombinations(const Terms &terms)
{
for (uint j = 0; j < combos.size(); j++)
{
QStringList term = _terms.at(i).components();
QStringList newTerm = combos.at(j).components();

term.append(newTerm);
newTerms.add(Term(term));
QStringList termComponents = _terms.at(i).components();
termComponents.append(combos.at(j).components());
columnTypeVec types = _terms.at(i).types();
columnTypeVec coTypes = combos.at(j).types();
types.insert(types.end(), coTypes.begin(), coTypes.end());

Term newTerm(termComponents);
newTerm.setTypes(types);
newTerms.add(newTerm);
}
}

Expand Down

0 comments on commit f86cf17

Please sign in to comment.