Gosse Minnema
Add sociofillmore code, load dataset via private dataset repo
b11ac48
(function ($) {
// Constants
const datasetInfo = {
"femicides/rai": "This dataset consists of news reports and metadata about femicides (i.e., murders in which the perpetrator, often a partner or ex-partner, kills a woman because of her gender) perpetrated in Italy between 2015-2017. The dataset has been compiled by the research unit of national public broadcaster RAI in collaboration with a team of sociologists. The news reports come from a variety of national and regional news outlets. Because of the rich set of available metadata (only a small part of which is, as of now, available in this tool), newspaper reports that report the same femicide can be linked, allowing for investigating how one and the same event is framed in different news reports, and how framing changes over time.",
"femicides/olv": "This dataset consists of news reports and metadata about femicides (i.e., murders in which the perpetrator, often a partner or ex-partner, kills a woman because of her gender) perpetrated in Italy between 2012-2019. The dataset consists of a set of case summaries and metadata (see https://27esimaora.corriere.it/la-strage-delle-donne/) compiled by journalists of Corriere della Sera, a major national newspaper, and a set of newspaper articles from several national newspapers mentioning these cases (automatically matched based on victim names).",
"migration/pavia": "This dataset consists of immigration-related news articles in Italy published between 2013-2021, collected by the Osservatorio di Pavia media research organization (https://www.osservatorio.it/) ",
"crashes/thecrashes": "This dataset consists of news articles in Dutch-language news outlets (mostly regional Dutch and Belgian newspapers) describing traffic crashes, together with metadata about the events. This dataset is developed by researchers at the University of Amsterdam, in with journalists at The Correspondent, and is publically available at https://nl.roaddanger.org/."
};
const datasetFrameInfo = {
"femicides/rai": "For investigating the conceptualization of femicides in the media, we have created three clusters of frames. The most important one, 'murder', comprises frames that describe the act of murder itself in various degrees of agentivity (KILLING implies an active agent, DEATH and DEAD_OR_ALIVE describe the death of the victim, and CATASTROPHE and EVENT present the murders as abstract events or tragedies).",
"femicides/olv": "For investigating the conceptualization of femicides in the media, we have created three clusters of frames. The most important one, 'murder', comprises frames that describe the act of murder itself in various degrees of agentivity (KILLING implies an active agent, DEATH and DEAD_OR_ALIVE describe the death of the victim, and CATASTROPHE and EVENT present the murders as abstract events or tragedies).",
"migration/pavia": "Several clusters of frames were created, reflecting different aspects of media coverage on migration, including the depiction of migrants as quantities ('a tsunami of migrants'), political aspects of migration, and integration of migrants into society.",
"crashes/thecrashes": "We defined a single cluster of frames that conceptualize several salient properties of traffic crashes, both from an agent/cause-centered perspective (KILLING, CAUSE_HARM) and from a non-agentive perspective (EVENT, EXPERIENCE_BODILY_HARM)"
};
/* === GLOBAL OBJECTS === */
/* --- global data/parameters --- */
// active dataset & model
let activeDataset = "femicides/olv"; // should be one of: "femicides/rai", "femicides/olv", "crashes/thecrashes", "migration/pavia"
let activeLomeModel = "lome_0shot"; // should be one of: "lome_0shot", "lome_evalita_plus_fn", "lome_zs-tgt_ev-frm"
// list of relevant frames
let relevantFrameCluster = "ALL";
let relevantFrames = {};
// list of active filters
let activeEventFilters = [];
let activeDocumentFilters = [];
// set of filters listed in the table
let tableFilters = new Set();
/* --- global jQuery objects --- */
// div for writing the output
const $framesOut = $("#frames-out");
// list of relevant frames (buttons)
const $frameList = $("#frame-list");
// list of possible documents
const $documentList = $("#document-list");
// statistics table rows
const $tableModalRows = $("#table-modal-rows");
// Which filters apply to which datasets?
const filtersToDatasets = {
"#doc-filter-days-after": ["rai", "olv", "thecrashes"],
"#doc-filter-provider": ["rai", "olv", "thecrashes", "pavia"],
"#doc-filter-politics-unipv": ["rai"],
"#doc-filter-politics-tc": ["rai"],
"#doc-filter-politics-agg": ["rai"],
"#doc-filter-politics-sc": ["pavia"],
"#doc-filter-religion-sc": ["pavia"],
"#doc-filter-type": ["rai"],
"#doc-filter-area": ["rai", "thecrashes"],
"#doc-filter-country": ["thecrashes"],
"#doc-filter-province": ["thecrashes"],
"#doc-filter-content-type": ["thecrashes"],
"#doc-filter-medium-type": ["thecrashes"],
"#doc-filter-owner": ["thecrashes"],
"#ev-filter-event-cat": ["rai"],
"#ev-filter-region": ["rai", "olv"],
"#ev-filter-semloc": ["rai"],
"#ev-filter-vict-age": ["rai", "olv"],
"#ev-filter-vict-nat": ["rai"],
"#ev-filter-injured-total": ["thecrashes"],
"#ev-filter-injured-child": ["thecrashes"],
"#ev-filter-injured-pedestrian": ["thecrashes"],
"#ev-filter-injured-cyclist": ["thecrashes"],
"#ev-filter-injured-cyclistpedestrian": ["thecrashes"],
"#ev-filter-injured-vehicle": ["thecrashes"],
"#ev-filter-dead-total": ["thecrashes"],
"#ev-filter-dead-child": ["thecrashes"],
"#ev-filter-dead-pedestrian": ["thecrashes"],
"#ev-filter-dead-cyclist": ["thecrashes"],
"#ev-filter-dead-cyclistpedestrian": ["thecrashes"],
"#ev-filter-dead-vehicle": ["thecrashes"],
"#ev-filter-deadinjured-total": ["thecrashes"],
"#ev-filter-deadinjured-child": ["thecrashes"],
"#ev-filter-deadinjured-pedestrian": ["thecrashes"],
"#ev-filter-deadinjured-cyclist": ["thecrashes"],
"#ev-filter-deadinjured-cyclistpedestrian": ["thecrashes"],
"#ev-filter-deadinjured-vehicle": ["thecrashes"],
"#ev-filter-imbalanced": ["thecrashes"],
"#ev-filter-att-nat": ["rai"],
"#ev-filter-vict-occ": ["rai"],
"#ev-filter-att-occ": ["rai"]
}
/* === FUNCTIONS: GETTING DATA FROM THE FLASK API === */
/* --- static data for feeding the option menus --- */
function getPossibleConstructions() {
const $constructionSampleList = $("#construction-sample-list, #construction-freq-select");
$constructionSampleList.empty();
$constructionSampleList.append($("<option>").text("ANY CONSTRUCTION").attr("value", "*"));
$.get("constructions").done((data) => {
data.forEach((construction) => {
$constructionSampleList
.append($("<option>")
.text(construction)
.attr("value", construction)
);
});
});
}
function getPossibleDependencies() {
const $depSampleList = $("#dep-sample-list");
$depSampleList.empty();
$depSampleList.append($("<option>").text("ANY DEPENDENCY").attr("value", "*"));
$.get("dep_labels").done((data) => {
data.forEach((depLabel) => {
$depSampleList.append($("<option>")
.text(depLabel)
.attr("value", depLabel)
);
});
});
}
function updateRoleSampleList(roles) {
const $roleSampleList = $("#role-sample-list");
$roleSampleList.empty();
$roleSampleList.append($("<option>").text("ANY ROLE").attr("value", "*"));
roles.forEach((role) => {
$roleSampleList.append($("<option>").text(role).attr("value", role));
})
}
function processFrames(frameList) {
let framesOut = {"ALL": [], "OTHER": []};
frameList.forEach((frame) => {
if (frame.includes("#")) {
let frameName = frame.split("#")[0];
let clusterName = frame.split("#")[1].toLowerCase();
if (!(clusterName in framesOut)) {
framesOut[clusterName] = [frameName];
}
else {
framesOut[clusterName].push(frameName);
}
framesOut["ALL"].push(frameName);
}
else {
framesOut["OTHER"].push(frame);
framesOut["ALL"].push(frame);
}
});
return framesOut;
}
function getRelevantFrames() {
$frameList.empty();
$.get("frames", { "dataset": activeDataset }).done(function (data) {
relevantFrames = processFrames(data);
// add cluster options
$frameClusters = $("#frame-clusters");
$frameClusters.empty();
let keys = Object.keys(relevantFrames);
keys.sort();
keys.forEach((cluster) => {
$frameClusters.append($("<option>").attr("value", cluster).text(cluster));
});
relevantFrames[relevantFrameCluster].forEach(function (frame) {
$frameList.append(addFrameButton(frame))
});
updateFrameSelectors();
})
}
function getEventFilters() {
$.get("event_filters").done((data) => {
// reset event filters
$("#ev-filters select").empty();
if (activeDataset === "femicides/rai") {
data["event_categories"].forEach((category) => {
$("#ev-filter-event-cat").append(makeFilterOption("e", "event:category", category));
});
data["regions"].forEach((region) => {
$("#ev-filter-region").append(makeFilterOption("e", "event:region", region));
});
data["sem_location"].forEach((location) => {
$("#ev-filter-semloc").append(makeFilterOption("e", "event:semantic_location", location));
});
data["victim_age"].forEach((age) => {
$("#ev-filter-vict-age").append(makeFilterOption("e", "victim:age", age));
});
data["victim_nationality"].forEach((nationality) => {
$("#ev-filter-vict-nat").append(makeFilterOption("e", "victim:nationality", nationality));
});
data["attacker_nationality"].forEach((nationality) => {
$("#ev-filter-att-nat").append(makeFilterOption("e", "attacker:nationality", nationality));
});
data["victim_occupation"].forEach((occupation) => {
$("#ev-filter-vict-occ").append(makeFilterOption("e", "victim:occupation", occupation));
});
data["attacker_occupation"].forEach((occupation) => {
$("#ev-filter-att-occ").append(makeFilterOption("e", "attacker:occupation", occupation));
});
} else if (activeDataset === "femicides/olv") {
data["regions"].forEach((region) => {
$("#ev-filter-region").append(makeFilterOption("e", "event:region", region));
});
data["victim_age"].forEach((age) => {
$("#ev-filter-vict-age").append(makeFilterOption("e", "victim:age", age));
});
} else if (activeDataset === "crashes/thecrashes") {
data["outcomes"].forEach((option) => {
Object.keys(filtersToDatasets)
.filter((key) => key.startsWith("#ev-filter-injured-") || key.startsWith("#ev-filter-dead-") || key.startsWith("#ev-filter-deadinjured-"))
.forEach((key) => {
const outcome = key.split("-")[2];
const people = key.split("-")[3];
$(key).append(makeFilterOption("e", `outcomes:${outcome}:${people}`, option));
})
});
data["imbalanced"].forEach((option) => {
$("#ev-filter-imbalanced").append(makeFilterOption("e", "imbalanced", option));
});
}
})
}
function getDocumentFilters(callback = null) {
$.get("doc_filters").done((data) => {
// reset selects
$("#doc-filters select").empty();
const daysAfterOptions = [
["within 1 day", "day"],
["between 1 day and 1 week", "week"],
["between 1 week and 1 month", "month"],
["between 1 month and 1 year", "year"],
["later", "later"]
]
daysAfterOptions.forEach((option) => {
$("#doc-filter-days-after").append(makeFilterOption("d", "days_after", option[1], option[0]));
})
data["providers"].forEach((provider) => {
$("#doc-filter-provider")
.append(makeFilterOption("d", "provider", provider));
});
if (activeDataset === "femicides/rai") {
["left", "right", "neutral"].forEach((stance) => {
// $("#doc-filter-politics-unipv").append(makeFilterOption("d", "politics:man", stance));
$("#doc-filter-politics-tc").append(makeFilterOption("d", "politics:tc", stance));
$("#doc-filter-politics-agg").append(makeFilterOption("d", "politics:agg", stance));
});
["national", "regional"].forEach((area) => {
$("#doc-filter-area").append(makeFilterOption("d", "area", area));
});
["agency", "outlet"].forEach((provType) => {
$("#doc-filter-type").append(makeFilterOption("d", "type", provType));
});
} else if (activeDataset === "crashes/thecrashes") {
["National", "Regional", "Local"].forEach((area) => {
$("#doc-filter-area").append(makeFilterOption("d", "area", area));
});
["Netherlands", "Belgium"].forEach((country) => {
$("#doc-filter-country").append(makeFilterOption("d", "country", country));
});
data["provider_provinces"].forEach((province) => {
$("#doc-filter-province").append(makeFilterOption("d", "province", province));
});
data["provider_content_types"].forEach((contentType) => {
$("#doc-filter-content-type").append(makeFilterOption("d", "content_type", contentType));
});
data["provider_medium_types"].forEach((mediumType) => {
$("#doc-filter-medium-type").append(makeFilterOption("d", "medium_type", mediumType));
})
data["provider_owners"].forEach((owner) => {
$("#doc-filter-owner").append(makeFilterOption("d", "owner", owner));
})
} else if (activeDataset === "migration/pavia") {
["left", "right", "neutral"].forEach((stance) => {
$("#doc-filter-politics-sc").append(makeFilterOption("d", "politics:sc", stance));
});
["catholic", "non_catholic"].forEach((stance) => {
$("#doc-filter-religion-sc").append(makeFilterOption("d", "religion:sc", stance));
});
}
if (callback !== null) {
callback();
}
});
}
function getDocumentList() {
let params = {
"event_filters": activeEventFilters.join("+"),
"doc_filters": activeDocumentFilters.join("+")
};
$.get("documents", params).done(function (data) {
const eventIdSet = new Set();
const docIdSet = new Set();
$documentList.empty();
data.forEach((doc) => {
eventIdSet.add(doc["event_id"])
docIdSet.add(doc["doc_id"])
$documentList.append($("<option>")
.text(`event ${doc["event_id"]} / doc ${doc["doc_id"]}`)
.attr("value", `${doc["event_id"]}:${doc["doc_id"]}`)
);
});
$(".event-doc-count").text(`(found: ${eventIdSet.size} events and ${docIdSet.size} documents)`)
});
}
/* === FILTER/PARAMETER OPTIONS === */
// Hide or show filter options depending on the selected dataset
function hideOrShowFilterOptions() {
let shortKey;
if (activeDataset === "femicides/rai") {
shortKey = "rai";
} else if (activeDataset === "femicides/olv") {
shortKey = "olv";
} else if (activeDataset === "crashes/thecrashes") {
shortKey = "thecrashes";
} else if (activeDataset === "migration/pavia") {
shortKey = "pavia";
} else {
throw Error("Unsupported dataset");
}
Object.entries(filtersToDatasets).forEach((entry) => {
let [filter, datasets] = entry;
if (datasets.includes(shortKey)) {
$(filter).parent().parent().show();
} else {
$(filter).parent().parent().hide();
}
})
}
// Add filter value entry to a dropdown menu
function makeFilterOption(type, attr, value, valueText = null) {
// type: "e" for event, "d" for doc
if (valueText === null) {
valueText = value;
}
return $("<option>").attr("value", `${type}::${attr}::${value}`).text(valueText);
}
// Add a relevant frame button
function addFrameButton(frame) {
return $("<button>")
.addClass("btn btn-info")
.text(frame)
.click(function (event) {
let $button = $(event.target);
//remove frame from the list
relevantFrames[relevantFrameCluster].splice(relevantFrames[relevantFrameCluster].indexOf($button.text()), 1);
if (relevantFrameCluster !== "ALL") {
relevantFrames["ALL"].splice(relevantFrames["ALL"].indexOf($button.text()), 1);
}
updateFrameSelectors();
console.log(relevantFrames);
$button.remove();
})
}
function updateFrameSelectors() {
let $frameSelectors = $("#frame-sample-list, #frame-freq-select");
$frameSelectors.empty();
$("#frame-sample-list").append($("<option>").text("ALL FRAMES").attr("value", "*"));
relevantFrames[relevantFrameCluster].forEach((frame) => {
$frameSelectors.append($("<option>")
.text(frame)
.attr("value", frame)
);
});
$.get("role_labels", { "frame": $("#frame-sample-list").val() }).done((roles) => {
updateRoleSampleList(roles);
})
}
// activate a filter & add a button for that filter
function activateFilter(filterValue) {
// filter events or documents?
const $filterList = filterValue.startsWith("e::") ? $("#event-filter-list") : $("#doc-filter-list");
const activeFilters = filterValue.startsWith("e::") ? activeEventFilters : activeDocumentFilters;
if (!activeFilters.includes(filterValue)) {
activeFilters.push(filterValue);
console.log(activeFilters);
// reload document list
getDocumentList();
// add self-deleting button
$filterList.append(
$("<button>")
.addClass("btn btn-secondary btn-sm mr-3")
.append($("<samp>").text(filterValue))
.click((event) => {
const $target = $(event.target);
const $button = $target.prop("nodeName") === "SAMP" ? $target.parent() : $target;
activeFilters.splice(activeFilters.indexOf($button.text()), 1);
getDocumentList();
console.log(activeFilters);
$button.remove();
})
);
}
}
/* === TEXT ANALYSIS === */
function formatFrameStructure(structure, deep, roleMapping) {
const targetString = `<b>Target:</b> ${structure["target"]["tokens_str"].join(" ")}`;
const targetIdx = structure["target"]["tokens_idx"][0].toString();
var synRoleDeps;
if (Object.keys(roleMapping).includes(targetIdx)) {
synRoleDeps = roleMapping[targetIdx];
} else {
synRoleDeps = {};
}
const roles = deep ? structure["deep_roles"] : structure["roles"]
// remove "Target" roles (EVALITA training bug) TODO: remove once EVALITA retraining is done
const filteredRoles = roles.filter((role) => role[0] !== "Target")
const roleStrings = filteredRoles.map((role) => {
let roleName = role[0];
let roleValue = role[1]["tokens_str"].join(" ");
let roleDep = "";
if (!deep) {
roleDep = Object.keys(synRoleDeps).includes(roleName) ? ` (<code>${synRoleDeps[roleName][0]}</code>)` : ""
}
return `<b>${roleName}${roleDep}:</b> ${roleValue}`;
});
return targetString + '<br>' + roleStrings.join("<br>");
}
function processSentence(sentenceData, index) {
let tokens = sentenceData["sentence"];
let tokenString = tokens.join(" ");
function getSyntaxInfo(struct) {
const targetIdx = struct["target"]["tokens_idx"][0].toString();
const syntaxForTarget = sentenceData["syntax"][targetIdx];
return syntaxForTarget[syntaxForTarget.length - 1];
}
function makeFrameButtons(filterRelevant, deep) {
// make frame buttons for relevant frames
let frameButtons = sentenceData["fn_structures"]
.filter(function (struct) {
const frameInRelevant = relevantFrames[relevantFrameCluster].includes(struct["frame"]);
return filterRelevant ? frameInRelevant : !frameInRelevant;
})
.map(function (struct) {
const syntaxInfo = getSyntaxInfo(struct);
const syntaxCat = syntaxInfo["syn_category"];
const syntaxConstr = syntaxInfo["syn_construction"];
const roleMapping = sentenceData["roles"];
return $("<button>")
.addClass(filterRelevant ? "btn btn-info text-white" : "btn btn-dark text-white btn-sm")
.text(deep ? struct["deep_frame"] : struct["frame"])
.attr("data-toggle", "popover")
.attr("data-html", true)
.attr("title", struct["frame"])
.attr("data-content", formatFrameStructure(struct, deep, roleMapping))
.append(
$("<span>")
.addClass("badge badge-light ml-1 text-primary")
.text(syntaxCat)
)
.append(
$("<span>")
.addClass("badge badge-light ml-1 text-danger")
.text(syntaxConstr)
)
.popover();
});
if (frameButtons.length === 0) {
frameButtons = [
$("<button>").addClass("btn btn-info text-white").prop("disabled", true).text("No frames found")
];
}
return frameButtons;
}
let relevantFrameButtons = makeFrameButtons(true, false);
let deepFrameButtons = makeFrameButtons(true, true);
let otherFrameButtons = makeFrameButtons(false, false);
function formatTextMeta(textMeta) {
return `<b>Provider:</b> ${textMeta["provider"]}`
+ `<br/><b>Title:</b> ${textMeta["title"]}`
+ `<br/><b>Published:</b> ${textMeta["pubdate"]}`
+ `<br/><b>Days after event:</b> ${textMeta["days_after_event"]}`
}
$framesOut
.append(
$("<div>")
.addClass("card bg-light mb-3")
.append(
$("<div>")
.addClass("card-header")
.text(`Sentence #${index} `)
.append($("<small>")
.text(`(event ${sentenceData["meta"]["event_id"]} / doc ${sentenceData["meta"]["doc_id"]})`))
.append($("<badge>")
.addClass("badge badge-dark text-white ml-2")
.text("text info")
.attr("data-toggle", "popover")
.attr("data-html", true)
.attr("data-content", formatTextMeta(sentenceData["meta"]["text_meta"]))
.popover()
)
)
.append(
// add card for each sentence
$("<div>")
.addClass("card-body")
// list group with different entries
.append($("<ul>")
.addClass("list-group list-group-flush")
// sentence string
.append($("<li>")
.addClass("list-group-item")
.append($("<h6>").text("Sentence:"))
.append($("<p>").text(tokenString)))
// frames of interest
.append($("<li>")
.addClass("list-group-item")
.append($("<h6>").text("Frames of interest:"))
.append($("<div>")
.addClass("btn-group flex-wrap")
.attr({ "role": "group" }).append(relevantFrameButtons)
)
)
// deep frames of interest
.append($("<li>")
.addClass("list-group-item")
.append($("<h6>").text("Deep Frames of interest:"))
.append($("<div>")
.addClass("btn-group flex-wrap")
.attr({ "role": "group" }).append(deepFrameButtons)
)
)
// other frames
.append($("<li>")
.addClass("list-group-item")
.append($("<h6>").text("Other frames:"))
.append($("<div>")
.addClass("btn-group flex-wrap")
.attr({ "role": "group" }).append(otherFrameButtons)
)
)
)
)
);
}
function processDocument(eventId, docId) {
$framesOut.empty();
$.get("analyze", { "event": eventId, "document": docId, "model": activeLomeModel }).done(function (data) {
data.forEach((element, index) => processSentence(element, index));
});
}
function processSamples(frame, construction, dependency, role) {
$framesOut.empty();
$framesOut.append($("<small>").text("Loading..."));
$.get("sample_frame", {
"model": activeLomeModel,
"frame": frame,
"construction": construction,
"dependency": dependency,
"role": role,
"event_filters": activeEventFilters.join("+"),
"doc_filters": activeDocumentFilters.join("+")
}).done((data) => {
$framesOut.empty();
if (data.length === 0) {
$framesOut.append($("<small>").text("No samples were found"))
} else {
data.forEach((element, index) => processSentence(element, index));
}
});
}
/* === COMPUTE & DISPLAY STATISTICS ===*/
// count and display frame frequency graphs
function showFrameFrequencies($button, frameFilter, constrFilter, groupByTgt, groupByCat, groupByConstr, groupByRoot, groupByRoleExpr) {
const oldText = $button.text();
$button.text("Loading...");
const useRelativeFreqs = $("#stats-relative").prop("checked");
const plotOverDaysPost = groupByRoleExpr ? false : $("#plot-over-days-post").prop("checked");
const plotByYear = groupByRoleExpr ? false : $("#plot-by-year").prop("checked");
const countOnlyHeadlines = $("#count-only-headlines").prop("checked");
if (frameFilter === "FROM_FORM") {
const frameToAnalyze = $("#frame-freq-select").val();
console.log(frameToAnalyze);
frameFilter = [frameToAnalyze];
}
if (constrFilter === "FROM_FORM") {
const formValue = $("#construction-freq-select").val();
constrFilter = formValue === "*" ? [] : [formValue];
} else if (constrFilter === "*") {
constrFilter = [];
}
const params = {
"model": activeLomeModel,
"frames": frameFilter.join("+"),
"constructions": constrFilter.join("+"),
"event_filters": activeEventFilters.join("+"),
"doc_filters": activeDocumentFilters.join("+"),
"group_by_cat": groupByCat ? "y" : "n",
"group_by_tgt": groupByTgt ? "y" : "n",
"group_by_constr": groupByConstr ? "y" : "n",
"group_by_root": groupByRoot ? "y" : "n",
"group_by_role_expr": groupByRoleExpr,
"relative": useRelativeFreqs ? "y" : "n",
"plot_over_days_post": plotOverDaysPost ? "y" : "n",
"headlines": countOnlyHeadlines ? "y" : "n",
"plot_by_year": plotByYear ? "y" : "n",
"days_time_window": $("#time-window").val()
};
$.get("frame_freq", params).done((data) => {
console.log(data);
$button.text(oldText);
const plotlyLayout = {
autosize: true,
// width: 800,
// height: 450,
showlegend: true,
legend: {"orientation": "v", "x": 1.0, "y": 0},
font: {"size": 20}
};
let relevantFramesPlotData = [];
let allFramesPlotData = [];
let deepFramesPlotData = [];
// let frameCountParts = ["relevant_frame_counts", "all_frame_counts", "deep_frame_counts"];
let frameCountParts = ["relevant_frame_counts"];
if (plotOverDaysPost || plotByYear) {
plotlyLayout["xaxis"] = {
"automargin": true,
"title": {
"text": plotOverDaysPost ? "days post event" : "years"
}
}
plotlyLayout["yaxis"] = {
"title": {
"text": "frame frequency"
}
}
frameCountParts.forEach((series) => {
Object.entries(data[series]).forEach((entry) => {
const [key, value] = entry;
let targetArray;
if (series === "relevant_frame_counts") {
targetArray = relevantFramesPlotData;
} else if (series === "all_frame_counts") {
targetArray = allFramesPlotData;
} else {
targetArray = deepFramesPlotData;
}
targetArray.push({
"x": value["x"],
"y": value["y"],
"stackgroup": "one",
"name": key,
});
})
})
} else {
const relevant = data["relevant_frame_counts"];
let numLabelParts = 2;
if (groupByRoleExpr === 1) {
numLabelParts = 1;
plotlyLayout["showlegend"] = false;
} else if (groupByRoleExpr > 1) {
numLabelParts = 3
}
plotlyLayout["barmode"] = "stack";
plotlyLayout["xaxis"] = {
"automargin": true,
"tickangle": 30,
"categoryorder": "total descending",
"title": {
"text": groupByRoleExpr > 0 ? "role" : "frame"
}
}
plotlyLayout["yaxis"] = {
// "range": [0, 1]
"title": {
"text": "frequency"
}
}
// filter by role sub-label
const roleSubLabels = new Set(relevant["x"].map((x) => x.split("::")[numLabelParts-1]));
const roleDepLabelArr = Array.from(roleSubLabels);
roleDepLabelArr.sort().reverse();
relevantFramesPlotData = roleDepLabelArr.map((label) => {
const xs = [];
const ys = [];
relevant["x"].forEach((x, idx) => {
if (numLabelParts > 1 && x.split("::")[numLabelParts-1] === label) {
xs.push(x.split("::").splice(0, numLabelParts-1).join("::"));
ys.push(relevant["y"][idx]);
} else if (numLabelParts == 1) {
xs.push(x.split("::")[1]);
ys.push(relevant["y"][idx]);
}
})
return {
x: xs,
y: ys,
name: label,
type: "bar"
}
});
// } else {
// relevantFramesPlotData = [{
// x: relevant["x"],
// y: relevant["y"],
// type: "bar"
// }];
// }
// const all = data["all_frame_counts"];
// allFramesPlotData = [{
// x: all["x"],
// y: all["y"],
// type: "bar"
// }];
// const deep = data["deep_frame_counts"];
// deepFramesPlotData = [{
// x: deep["x"],
// y: deep["y"],
// type: "bar"
// }];
// }
}
let plotlyRelevantFrames = "frame-freq-relevant-plotly";
// let plotlyDeepFrames = "deep-frame-freq-plotly";
// let plotlyAllFrames = "frame-freq-all-plotly";
Plotly.newPlot(plotlyRelevantFrames, relevantFramesPlotData, plotlyLayout);
// Plotly.newPlot(plotlyDeepFrames, deepFramesPlotData);
// Plotly.newPlot(plotlyAllFrames, allFramesPlotData);
$("#frame-freq-modal").modal().on("shown.bs.modal", () => {
Plotly.relayout(plotlyRelevantFrames, { autosize: true })
// Plotly.relayout(plotlyDeepFrames, { autosize: true })
// Plotly.relayout(plotlyAllFrames, { autosize: true })
});
});
}
// show statistics table for specific frame
function showStatsTable(frame) {
if (frame === "FROM_FORM") {
frame = $("#frame-freq-select").val();
}
if (frame === "*") {
frame = "";
}
const $tableModal = $("#table-modal");
function roundToDecimals(num, decimals) {
const multiplier = Math.pow(10, decimals)
// from https://stackoverflow.com/questions/11832914/how-to-round-to-at-most-2-decimal-places-if-necessary
return Math.round((num + Number.EPSILON) * multiplier) / multiplier;
}
// build a string representing all currently active filters
const allFilters = activeEventFilters.concat(activeDocumentFilters);
allFilters.sort();
const filterString = (
frame + "__"
+ (allFilters.length > 0 ? allFilters.join("+") : "no filters")
+ "__" + activeLomeModel
);
// make sure the entry is not already in the table
if (tableFilters.has(filterString)) {
$tableModal.modal();
return;
}
const $tableModalButton = $("#constr-table-btn");
const oldText = $tableModalButton.text();
$tableModalButton.text("Loading...");
// add to filter set
tableFilters.add(filterString);
const rowID = tableFilters.size;
let options = {
"model": activeLomeModel,
"frames": frame,
"constructions": "",
"event_filters": activeEventFilters.join("+"),
"doc_filters": activeDocumentFilters.join("+"),
"group_by_cat": "n",
"group_by_constr": "y",
"group_by_role_expr": 0,
"plot_over_days_post": "n",
"relative": "y"
};
console.log(options);
$.get("frame_freq", options).done((data) => {
$tableModalButton.text(oldText);
let countMapping = {};
data["relevant_frame_counts"]["x"].forEach((construction, idx) => {
countMapping[construction] = roundToDecimals(data["relevant_frame_counts"]["y"][idx], 3);
});
$tableModal.modal()
const filterStringDisplay = filterString.split("__")[1].replace("+", "<br>")
const $tableRows = $("<tr>")
.append($(`<th scope='row'>${rowID}</th>`))
.append($(`<td><code>${activeLomeModel}</code></td>`))
.append($(`<td><code>${frame}</code></td>`))
.append($(`<td><code>${filterStringDisplay}</code></td>`));
const countKeys = [
`${frame}::nonverbal`,
`${frame}::verbal:active`,
`${frame}::verbal:passive_unaccusative`,
`${frame}::verbal:reflexive`,
`${frame}::other`
]
countKeys.forEach((key) => {
const countValue = key in countMapping ? countMapping[key] : 0.0;
$tableRows.append(`<td>${countValue}</td>`);
})
$tableModalRows.append($tableRows);
})
}
function switchInitializeDataset() {
// temporally disable the switching buttons
const $switchButtons = $("#dataset-switch a");
$switchButtons.addClass("disabled");
// add dataset information
$("#dataset-info").text(datasetInfo[activeDataset]);
$("#dataset-frame-info").text(datasetFrameInfo[activeDataset]);
$.get("switch_dataset", { "dataset": activeDataset }).done(() => {
console.log(`SERVER: switched dataset to ${activeDataset}`);
// if (activeDataset === "migration/pavia") $("#lome-model-combined").click();
// // switch to the 0shot model
// else
$("#lome-model-0shot").click();
// disable/enable EVALITA option
$("#lome-model-switch a").addClass("disabled");
if (activeDataset === "femicides/rai") {
$("#lome-model-0shot").removeClass("disabled");
$("#lome-model-evalita").removeClass("disabled");
}
if (activeDataset === "migration/pavia") {
$("#lome-model-0shot").removeClass("disabled");
}
else {
$("#lome-model-0shot").removeClass("disabled");
}
// clear stats table
tableFilters.clear();
$tableModalRows.empty();
// clear filters
$("#doc-filter-list button, #ev-filter-list-button").click();
// retrieve dataset-specific info
getRelevantFrames();
getPossibleConstructions();
getPossibleDependencies();
getEventFilters();
getDocumentFilters(() => {
// enable the switching buttons again once filters are loaded
$switchButtons.removeClass("disabled");
});
getDocumentList();
})
}
/* === MAIN === */
/* --- on page load --- */
$(function () {
hideOrShowFilterOptions();
switchInitializeDataset();
});
$(".pwd-prompt").hide();
/* --- event handlers --- */
$("#dataset-switch a").click((event) => {
console.log("Switching datasets...");
relevantFrameCluster = "ALL";
const datasetKeys = [
"femicides/olv",
"femicides/rai",
"crashes/thecrashes",
"migration/pavia"
];
const $datasetOptions = [
$("#femicides-olv"),
$("#femicides-rai"),
$("#crashes-thecrashes"),
$("#migration-pavia")
];
const protectedDatasets = [
// "femicides/rai"
];
$datasetOptions.forEach(($option, idx) => {
if ($(event.target).is($option)) {
activeDataset = datasetKeys[idx];
$("#dataset-switch a").removeClass("active");
$option.addClass("active");
}
})
if (protectedDatasets.includes(activeDataset)) {
console.log("hiding protected elements");
$(".pwd-protected").hide();
$(".pwd-prompt").show();
} else {
$(".pwd-protected").show();
$(".pwd-prompt").hide();
}
// hide or show filter options depending on the selected dataset
hideOrShowFilterOptions();
switchInitializeDataset();
});
$("#pwd-submit").click(() => {
let pwd = $("#password-input").val();
$.post("/check_password", {"password": pwd}).done((result) => {
if (result["success"]) {
$(".pwd-protected").show();
$(".pwd-prompt").hide();
hideOrShowFilterOptions();
switchInitializeDataset();
}
})
});
$("#lome-model-switch a").click((event) => {
console.log("Switching LOME models...");
const modelKeys = [
"lome_0shot",
"lome_evalita_plus_fn",
"lome_zs-tgt_ev-frm"
]
const $modelOptions = [
$("#lome-model-0shot"),
$("#lome-model-evalita"),
$("#lome-model-combined"),
]
$modelOptions.forEach(($option, idx) => {
if ($(event.target).is($option)) {
activeLomeModel = modelKeys[idx];
$("#lome-model-switch a").removeClass("active");
$option.addClass("active");
}
})
})
$("#add-frame-button").click(() => {
let $addFrameInput = $("#add-frame-input");
let newFrame = $addFrameInput.val();
if (!relevantFrames[relevantFrameCluster].includes(newFrame)) {
relevantFrames[relevantFrameCluster].push(newFrame);
}
if (!relevantFrames["ALL"].includes(newFrame)) {
relevantFrames["ALL"].push(newFrame);
}
console.log(relevantFrames);
updateFrameSelectors();
$addFrameInput.val("");
$frameList.append(addFrameButton(newFrame));
});
$("#frame-clusters").change((event) => {
relevantFrameCluster = $(event.target).val();
$frameList.empty();
relevantFrames[relevantFrameCluster].forEach(function (frame) {
$frameList.append(addFrameButton(frame))
});
});
$("#filter-options button").click((event) => {
const $select = ($(event.target).parent().siblings("select"));
const value = $select.val();
activateFilter(value);
});
$("#analyze-doc-button").click(() => {
const docVal = $documentList.val();
const eventId = docVal.split(":")[0];
const docId = docVal.split(":")[1];
processDocument(eventId, docId);
});
$("#frame-sample-list").change((event) => {
const newFrame = $(event.target).val();
$.get("role_labels", { "frame": newFrame }).done((data) => {
updateRoleSampleList(data);
});
});
$("#sample-frame-button").click(() => {
const frameVal = $("#frame-sample-list").val();
const constructionVal = $("#construction-sample-list").val();
const dependencyVal = $("#dep-sample-list").val();
const roleVal = $("#role-sample-list").val();
processSamples(frameVal, constructionVal, dependencyVal, roleVal);
});
const $frameFrequencies = $("#frame-frequencies");
const $frameTgtFrequencies = $("#frame-tgt-frequencies");
const $frameCatFrequencies = $("#frame-syncat-frequencies");
const $frameConstrFrequencies = $("#frame-cx-frequencies");
const $frameRootFrequencies = $("#frame-root-frequencies");
const $frameConstrRootFrequencies = $("#frame-cx-root-frequencies");
$frameFrequencies.click(() => showFrameFrequencies($frameFrequencies, relevantFrames[relevantFrameCluster], "*", false, false, false, false, 0));
$frameTgtFrequencies.click(() => showFrameFrequencies($frameTgtFrequencies, relevantFrames[relevantFrameCluster], "*", true, false, false, false, 0));
$frameCatFrequencies.click(() => showFrameFrequencies($frameCatFrequencies, relevantFrames[relevantFrameCluster], "*", false, true, false, false, 0));
$frameConstrFrequencies.click(() => showFrameFrequencies($frameConstrFrequencies, relevantFrames[relevantFrameCluster], "*", false, false, true, false, 0));
$frameRootFrequencies.click(() => showFrameFrequencies($frameRootFrequencies, relevantFrames[relevantFrameCluster], "*", false, false, false, true, 0));
$frameConstrRootFrequencies.click(() => showFrameFrequencies($frameConstrRootFrequencies, relevantFrames[relevantFrameCluster], "*", false, false, true, true, 0));
const $roleFrequencies = $("#role-frequencies");
const $roleExpFrequencies = $("#role-exp-frequencies");
const $rolePlExpFrequencies = $("#role-pl-exp-frequencies");
const $roleExpDepthFrequencies = $("#role-exp-depth-frequencies");
const $killingStatsTable = $("#constr-table-btn");
$roleFrequencies.click(() => showFrameFrequencies($roleFrequencies, "FROM_FORM", "FROM_FORM", false, false, false, false, 1));
$roleExpFrequencies.click(() => showFrameFrequencies($roleExpFrequencies, "FROM_FORM", "FROM_FORM", false, false, false, false, 2));
$rolePlExpFrequencies.click(() => showFrameFrequencies($rolePlExpFrequencies, "FROM_FORM", "FROM_FORM", false, false, false, false, 3));
$roleExpDepthFrequencies.click(() => showFrameFrequencies($roleExpDepthFrequencies, "FROM_FORM", "FROM_FORM", false, false, false, false, 4));
$killingStatsTable.click(() => showStatsTable("FROM_FORM"));
})
(jQuery);