Commit 45528571 authored by twanvl's avatar twanvl

Keyword usage can now be used for statistics

parent 3604888a
...@@ -23,6 +23,7 @@ StatsDimension::StatsDimension() ...@@ -23,6 +23,7 @@ StatsDimension::StatsDimension()
, position_hint(0) , position_hint(0)
, numeric (false) , numeric (false)
, show_empty (false) , show_empty (false)
, split_list (false)
{} {}
StatsDimension::StatsDimension(const Field& field) StatsDimension::StatsDimension(const Field& field)
...@@ -33,6 +34,7 @@ StatsDimension::StatsDimension(const Field& field) ...@@ -33,6 +34,7 @@ StatsDimension::StatsDimension(const Field& field)
, icon_filename(field.icon_filename) , icon_filename(field.icon_filename)
, numeric (false) , numeric (false)
, show_empty (false) , show_empty (false)
, split_list (false)
{ {
// choice field? // choice field?
const ChoiceField* choice_field = dynamic_cast<const ChoiceField*>(&field); const ChoiceField* choice_field = dynamic_cast<const ChoiceField*>(&field);
...@@ -72,6 +74,7 @@ IMPLEMENT_REFLECTION_NO_GET_MEMBER(StatsDimension) { ...@@ -72,6 +74,7 @@ IMPLEMENT_REFLECTION_NO_GET_MEMBER(StatsDimension) {
REFLECT(script); REFLECT(script);
REFLECT(numeric); REFLECT(numeric);
REFLECT(show_empty); REFLECT(show_empty);
REFLECT(split_list);
REFLECT(colors); REFLECT(colors);
REFLECT(groups); REFLECT(groups);
} }
......
...@@ -35,6 +35,7 @@ class StatsDimension : public IntrusivePtrBase<StatsDimension> { ...@@ -35,6 +35,7 @@ class StatsDimension : public IntrusivePtrBase<StatsDimension> {
OptionalScript script; ///< Script that determines the value(s) OptionalScript script; ///< Script that determines the value(s)
bool numeric; ///< Are the values numeric? If so, they require special sorting bool numeric; ///< Are the values numeric? If so, they require special sorting
bool show_empty; ///< Should "" be shown? bool show_empty; ///< Should "" be shown?
bool split_list; ///< Split values into multiple ones separated by commas
map<String,Color> colors; ///< Colors for the categories map<String,Color> colors; ///< Colors for the categories
vector<String> groups; ///< Order of the items vector<String> groups; ///< Order of the items
......
...@@ -37,6 +37,24 @@ GraphElement::GraphElement(const String& v1, const String& v2) { ...@@ -37,6 +37,24 @@ GraphElement::GraphElement(const String& v1, const String& v2) {
values.push_back(v2); values.push_back(v2);
} }
void GraphDataPre::splitList(size_t axis) {
size_t count = elements.size(); // only the elements that were already there
for (size_t i = 0 ; i < count ; ++i) {
GraphElement& e = *elements[i];
String& v = e.values[axis];
size_t comma = v.find_first_of(_(','));
while (comma != String::npos) {
// split
GraphElementP e2(new GraphElement(e));
e2->values[axis] = v.substr(0,comma);
elements.push_back(e2);
if (is_substr(v, comma, _(", "))) ++comma; // skip space after it
v = v.substr(comma + 1);
comma = v.find_first_of(_(','));
}
}
}
GraphData::GraphData(const GraphDataPre& d) GraphData::GraphData(const GraphDataPre& d)
: axes(d.axes) : axes(d.axes)
...@@ -50,13 +68,12 @@ GraphData::GraphData(const GraphDataPre& d) ...@@ -50,13 +68,12 @@ GraphData::GraphData(const GraphDataPre& d)
FOR_EACH_CONST(e, d.elements) { FOR_EACH_CONST(e, d.elements) {
counts[e->values[i]] += 1; counts[e->values[i]] += 1;
} }
// TODO: allow some ordering in the groups
if (a->numeric) { if (a->numeric) {
// TODO: start at something other than 0? // TODO: start at something other than 0?
// TODO: support fractions? // TODO: support fractions?
size_t left = counts.size(); size_t left = counts.size();
int i = 0; int i = 0;
while (left) { while (!counts.empty() && i < 100) {
String is = String() << i++; String is = String() << i++;
map<String,UInt>::const_iterator it = counts.find(is); map<String,UInt>::const_iterator it = counts.find(is);
if (it == counts.end()) { if (it == counts.end()) {
...@@ -66,16 +83,19 @@ GraphData::GraphData(const GraphDataPre& d) ...@@ -66,16 +83,19 @@ GraphData::GraphData(const GraphDataPre& d)
a->groups.push_back(GraphGroup(is, it->second)); a->groups.push_back(GraphGroup(is, it->second));
a->max = max(a->max, it->second); a->max = max(a->max, it->second);
a->total += it->second; a->total += it->second;
counts.erase(is);
left--; left--;
} }
if (i > 100) { }
// prevent infinite loops if there are non-numeric entries // Also keep non-numeric entries
// drop empty tail FOR_EACH(c, counts) {
while (a->groups.size() > 1 && a->groups.back().size == 0) { a->groups.push_back(GraphGroup(c.first, c.second));
a->groups.pop_back(); a->max = max(a->max, c.second);
} a->total += c.second;
break; }
} // drop empty tail
while (a->groups.size() > 1 && a->groups.back().size == 0) {
a->groups.pop_back();
} }
} else if (a->order) { } else if (a->order) {
// specific group order // specific group order
......
...@@ -87,6 +87,8 @@ class GraphDataPre { ...@@ -87,6 +87,8 @@ class GraphDataPre {
public: public:
vector<GraphAxisP> axes; vector<GraphAxisP> axes;
vector<GraphElementP> elements; vector<GraphElementP> elements;
/// Split compound elements, "a,b,c" -> "a" and "b" and "c"
void splitList(size_t axis);
}; };
/// Data to be displayed in a graph /// Data to be displayed in a graph
......
...@@ -147,6 +147,8 @@ void StatsPanel::onCommand(int id) { ...@@ -147,6 +147,8 @@ void StatsPanel::onCommand(int id) {
// ----------------------------------------------------------------------------- : Filtering card list // ----------------------------------------------------------------------------- : Filtering card list
bool chosen(const String& choice, const String& input);
class StatsFilter : public CardListFilter { class StatsFilter : public CardListFilter {
public: public:
StatsFilter(Set& set) StatsFilter(Set& set)
...@@ -155,7 +157,13 @@ class StatsFilter : public CardListFilter { ...@@ -155,7 +157,13 @@ class StatsFilter : public CardListFilter {
virtual bool keep(const CardP& card) { virtual bool keep(const CardP& card) {
Context& ctx = set.getContext(card); Context& ctx = set.getContext(card);
FOR_EACH(v, values) { FOR_EACH(v, values) {
if (v.first->script.invoke(ctx)->toString() != v.second) return false; StatsDimension& dim = *v.first;
String value = dim.script.invoke(ctx)->toString();
if (dim.split_list) {
if (!chosen(v.second, value)) return false;
} else {
if (value != v.second) return false;
}
} }
return true; return true;
} }
...@@ -208,6 +216,12 @@ void StatsPanel::showCategory() { ...@@ -208,6 +216,12 @@ void StatsPanel::showCategory() {
d.elements.push_back(e); d.elements.push_back(e);
} }
} }
// split lists
size_t dim_id = 0;
FOR_EACH(dim, cat.dimensions) {
if (dim->split_list) d.splitList(dim_id);
++dim_id;
}
graph->setLayout(cat.type); graph->setLayout(cat.type);
graph->setData(d); graph->setData(d);
filterCards(); filterCards();
......
...@@ -297,6 +297,30 @@ SCRIPT_RULE_2_DEPENDENCIES(expand_keywords) { ...@@ -297,6 +297,30 @@ SCRIPT_RULE_2_DEPENDENCIES(expand_keywords) {
SCRIPT_RETURN(_("")); SCRIPT_RETURN(_(""));
} }
SCRIPT_FUNCTION(keyword_usage) {
SCRIPT_PARAM(CardP, card);
SCRIPT_OPTIONAL_PARAM_(bool, unique);
// make a list "kw1, kw2, kw3" of keywords used on card
String ret;
for (KeywordUsageStatistics::const_iterator it = card->keyword_usage.begin() ; it != card->keyword_usage.end() ; ++it) {
bool keep = true;
if (unique) {
// prevent duplicates
for (KeywordUsageStatistics::const_iterator it2 = card->keyword_usage.begin() ; it != it2 ; ++it2) {
if (it->second == it2->second) {
keep = false;
break;
}
}
}
if (keep) {
if (!ret.empty()) ret += _(", ");
ret += it->second->keyword;
}
}
SCRIPT_RETURN(ret);
}
// ----------------------------------------------------------------------------- : Rules : regex replace // ----------------------------------------------------------------------------- : Rules : regex replace
class ScriptReplaceRule : public ScriptValue { class ScriptReplaceRule : public ScriptValue {
...@@ -677,6 +701,7 @@ void init_script_basic_functions(Context& ctx) { ...@@ -677,6 +701,7 @@ void init_script_basic_functions(Context& ctx) {
// keyword // keyword
ctx.setVariable(_("expand keywords"), script_expand_keywords); ctx.setVariable(_("expand keywords"), script_expand_keywords);
ctx.setVariable(_("expand keywords rule"), script_expand_keywords_rule); ctx.setVariable(_("expand keywords rule"), script_expand_keywords_rule);
ctx.setVariable(_("keyword usage"), script_keyword_usage);
// advanced string rules // advanced string rules
ctx.setVariable(_("replace"), script_replace); ctx.setVariable(_("replace"), script_replace);
ctx.setVariable(_("filter"), script_filter); ctx.setVariable(_("filter"), script_filter);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment