improved namesbase analysis

This commit is contained in:
Azgaar 2022-01-15 17:19:55 +03:00
parent acacf395f6
commit dbe84ea015
3 changed files with 100 additions and 67 deletions

View file

@ -35,8 +35,7 @@ input[type="radio"] {
} }
textarea { textarea {
padding: 2px; padding: 3px;
text-indent: 1px;
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
} }
@ -2012,7 +2011,7 @@ div.textual fieldset {
div.textual span, div.textual span,
.textual legend { .textual legend {
font-size: 0.8em; font-size: 0.9em;
font-weight: bold; font-weight: bold;
} }

View file

@ -2875,21 +2875,22 @@
<div id="namesbaseBasesTop"> <div id="namesbaseBasesTop">
<span>Select base: </span> <span>Select base: </span>
<select id="namesbaseSelect" data-tip="Select base to edit" style="width: 12em" value="0"></select> <select id="namesbaseSelect" data-tip="Select base to edit" style="width: 12em" value="0"></select>
<span style="margin-left: 2px">Names data: </span>
</div> </div>
<div id="namesbaseBody"> <div id="namesbaseBody" style="margin-block: 2px">
<span>Names data:</span><br> <textarea id="namesbaseTextarea" data-base="0" rows=13 data-tip="Names data: a comma separated list of source names used for names generation" placeholder="Provide a names data: a comma separated list of source names" autocorrect="off" spellcheck="false"></textarea>
<textarea id="namesbaseTextarea" data-base="0" rows=12 data-tip="Names data: a comma separated list of source names used for names generation" placeholder="Provide a names data: a comma separated list of source names" autocorrect="off" spellcheck="false"></textarea>
<br>
<div> <div>
<span>Name: </span> <span>Name: </span>
<input id="namesbaseName" data-tip="Type to change a base name" placeholder="Base name" autocorrect="off" spellcheck="false" style="width:12em"/> <input id="namesbaseName" data-tip="Type to change a base name" placeholder="Base name" autocorrect="off" spellcheck="false" style="width:12em"/>
<span>Length: </span> <span>Length: </span>
<input id="namesbaseMin" data-tip="Recommended minimum name length" type="number" min=2 max=100> <input id="namesbaseMin" data-tip="Recommended minimum name length" type="number" min=2 max=100>
<input id="namesbaseMax" data-tip="Recommended maximum name length" type="number" min=2 value=10> <input id="namesbaseMax" data-tip="Recommended maximum name length" type="number" min=2 value=10>
<span>Double: </span> <span>Doubled: </span>
<input id="namesbaseDouble" data-tip="Populate with letters that can used twice in a row (geminates)" autocorrect="off" spellcheck="false" style="width:10em"> <input id="namesbaseDouble" data-tip="Populate with letters that can used twice in a row (geminates)" autocorrect="off" spellcheck="false" style="width:10em">
</div> </div>
<fieldset> <fieldset>
<legend>Generated examples: </legend> <legend>Generated examples: </legend>
<div id="namesbaseExamples" data-tip="Examples. Click to re-generate"></div> <div id="namesbaseExamples" data-tip="Examples. Click to re-generate"></div>
@ -2898,12 +2899,12 @@
<div id="namesbaseBottom"> <div id="namesbaseBottom">
<button id="namesbaseUpdateExamples" data-tip="Re-generate examples based on provided data" class="icon-arrows-cw"></button> <button id="namesbaseUpdateExamples" data-tip="Re-generate examples based on provided data" class="icon-arrows-cw"></button>
<button id="namesbaseAnalize" data-tip="Analyze namesbase to get a validity and quality overview" class="icon-flask"></button>
<button id="namesbaseAdd" data-tip="Add new namesbase" class="icon-plus"></button> <button id="namesbaseAdd" data-tip="Add new namesbase" class="icon-plus"></button>
<button id="namesbaseDefault" data-tip="Restore default namesbase" class="icon-cancel"></button> <button id="namesbaseDefault" data-tip="Restore default namesbase" class="icon-cancel"></button>
<button id="namesbaseDownload" data-tip="Download namesbase to PC" class="icon-download"></button> <button id="namesbaseDownload" data-tip="Download namesbase to PC" class="icon-download"></button>
<button id="namesbaseUpload" data-tip="Upload a namesbase from PC" class="icon-upload"></button> <button id="namesbaseUpload" data-tip="Upload a namesbase from PC" class="icon-upload"></button>
<button id="namesbaseCA" data-tip="Find or share custom namesbase on Cartography Assets portal" class="icon-drafting-compass" onclick="openURL('https://cartographyassets.com/asset-category/specific-assets/azgaars-generator/namebases/')"></button> <button id="namesbaseCA" data-tip="Find or share custom namesbase on Cartography Assets portal" class="icon-drafting-compass"></button>
<button id="namesbaseAnalyze" data-tip="Analyze namesbase to get a validity and quality overview" class="icon-flask"></button>
<button id="namesbaseSpeak" data-tip="Speak the examples. You can change voice and language in options" class="icon-voice"></button> <button id="namesbaseSpeak" data-tip="Speak the examples. You can change voice and language in options" class="icon-voice"></button>
</div> </div>
</div> </div>

View file

@ -17,18 +17,24 @@ function editNamesbase() {
document.getElementById("namesbaseMax").addEventListener("input", updateBaseMax); document.getElementById("namesbaseMax").addEventListener("input", updateBaseMax);
document.getElementById("namesbaseDouble").addEventListener("input", updateBaseDublication); document.getElementById("namesbaseDouble").addEventListener("input", updateBaseDublication);
document.getElementById("namesbaseAdd").addEventListener("click", namesbaseAdd); document.getElementById("namesbaseAdd").addEventListener("click", namesbaseAdd);
document.getElementById("namesbaseAnalize").addEventListener("click", analizeNamesbase); document.getElementById("namesbaseAnalyze").addEventListener("click", analyzeNamesbase);
document.getElementById("namesbaseDefault").addEventListener("click", namesbaseRestoreDefault); document.getElementById("namesbaseDefault").addEventListener("click", namesbaseRestoreDefault);
document.getElementById("namesbaseDownload").addEventListener("click", namesbaseDownload); document.getElementById("namesbaseDownload").addEventListener("click", namesbaseDownload);
document.getElementById("namesbaseUpload").addEventListener("click", () => namesbaseToLoad.click()); document.getElementById("namesbaseUpload").addEventListener("click", () => document.getElementById("namesbaseToLoad").click());
document.getElementById("namesbaseToLoad").addEventListener("change", function() {uploadFile(this, namesbaseUpload)}); document.getElementById("namesbaseToLoad").addEventListener("change", function () {
uploadFile(this, namesbaseUpload);
});
document.getElementById("namesbaseCA").addEventListener("click", () => {
openURL("https://cartographyassets.com/asset-category/specific-assets/azgaars-generator/namebases/");
});
document.getElementById("namesbaseSpeak").addEventListener("click", () => speak(namesbaseExamples.textContent)); document.getElementById("namesbaseSpeak").addEventListener("click", () => speak(namesbaseExamples.textContent));
createBasesList(); createBasesList();
updateInputs(); updateInputs();
$("#namesbaseEditor").dialog({ $("#namesbaseEditor").dialog({
title: "Namesbase Editor", width: "42.5em", title: "Namesbase Editor",
width: "auto",
position: {my: "center", at: "center", of: "svg"} position: {my: "center", at: "center", of: "svg"}
}); });
@ -40,7 +46,10 @@ function editNamesbase() {
function updateInputs() { function updateInputs() {
const base = +document.getElementById("namesbaseSelect").value; const base = +document.getElementById("namesbaseSelect").value;
if (!nameBases[base]) {tip(`Namesbase ${base} is not defined`, false, "error"); return;} if (!nameBases[base]) {
tip(`Namesbase ${base} is not defined`, false, "error");
return;
}
document.getElementById("namesbaseTextarea").value = nameBases[base].b; document.getElementById("namesbaseTextarea").value = nameBases[base].b;
document.getElementById("namesbaseName").value = nameBases[base].name; document.getElementById("namesbaseName").value = nameBases[base].name;
document.getElementById("namesbaseMin").value = nameBases[base].min; document.getElementById("namesbaseMin").value = nameBases[base].min;
@ -52,7 +61,7 @@ function editNamesbase() {
function updateExamples() { function updateExamples() {
const base = +document.getElementById("namesbaseSelect").value; const base = +document.getElementById("namesbaseSelect").value;
let examples = ""; let examples = "";
for (let i=0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const example = Names.getBase(base); const example = Names.getBase(base);
if (example === undefined) { if (example === undefined) {
examples = "Cannot generate examples. Please verify the data"; examples = "Cannot generate examples. Please verify the data";
@ -84,13 +93,19 @@ function editNamesbase() {
function updateBaseMin() { function updateBaseMin() {
const base = +document.getElementById("namesbaseSelect").value; const base = +document.getElementById("namesbaseSelect").value;
if (+this.value > nameBases[base].max) {tip("Minimal length cannot be greater than maximal", false, "error"); return;} if (+this.value > nameBases[base].max) {
tip("Minimal length cannot be greater than maximal", false, "error");
return;
}
nameBases[base].min = +this.value; nameBases[base].min = +this.value;
} }
function updateBaseMax() { function updateBaseMax() {
const base = +document.getElementById("namesbaseSelect").value; const base = +document.getElementById("namesbaseSelect").value;
if (+this.value < nameBases[base].min) {tip("Maximal length should be greater than minimal", false, "error"); return;} if (+this.value < nameBases[base].min) {
tip("Maximal length should be greater than minimal", false, "error");
return;
}
nameBases[base].max = +this.value; nameBases[base].max = +this.value;
} }
@ -99,59 +114,70 @@ function editNamesbase() {
nameBases[base].d = this.value; nameBases[base].d = this.value;
} }
function analizeNamesbase() { function analyzeNamesbase() {
const string = document.getElementById("namesbaseTextarea").value; const namesSourceString = document.getElementById("namesbaseTextarea").value;
if (!string) {tip("Names data field should not be empty", false, "error"); return;} const namesArray = namesSourceString.toLowerCase().split(",");
const base = string.toLowerCase(); const length = namesArray.length;
const array = base.split(","); if (!namesSourceString || !length) return tip("Names data should not be empty", false, "error");
const l = array.length;
if (!l) {tip("Names data should not be empty", false, "error"); return;}
const wordsLength = array.map(n => n.length); const chain = Names.calculateChain(namesSourceString);
const multi = rn(d3.mean(array.map(n => (n.match(/ /i)||[]).length)) * 100, 2); const variety = rn(d3.mean(Object.values(chain).map(keyValue => keyValue.length)));
const geminate = array.map(name => name.match(/[^\w\s]|(.)(?=\1)/g)||[]).flat();
const doubled = ([...new Set(geminate)].filter(l => geminate.filter(d => d === l).length > 3)||["none"]).join("");
const chain = Names.calculateChain(string);
const depth = rn(d3.mean(Object.keys(chain).map(key => chain[key].filter(c => c !== " ").length)));
const nonLatin = (string.match(/[^\u0000-\u007f]/g)||["none"]).join("");
const lengthStat = const wordsLength = namesArray.map(n => n.length);
l < 30 ? "<span style='color:red'>[not enough]</span>" :
l < 150 ? "<span style='color:darkred'>[low]</span>" :
l < 150 ? "<span style='color:orange'>[low]</span>" :
l < 400 ? "<span style='color:green'>[good]</span>" :
l < 600 ? "<span style='color:orange'>[overmuch]</span>" :
"<span style='color:darkred'>[overmuch]</span>";
const rangeStat = const nonLatin = namesSourceString.match(/[^\u0000-\u007f]/g);
l < 10 ? "<span style='color:red'>[low]</span>" : const nonBasicLatinChars = nonLatin
l < 15 ? "<span style='color:darkred'>[low]</span>" : ? unique(
l < 20 ? "<span style='color:orange'>[low]</span>" : namesSourceString
"<span style='color:green'>[good]</span>"; .match(/[^\u0000-\u007f]/g)
.join("")
.toLowerCase()
).join("")
: "none";
const depthStat = const geminate = namesArray.map(name => name.match(/[^\w\s]|(.)(?=\1)/g) || []).flat();
l < 15 ? "<span style='color:red'>[low]</span>" : const doubled = unique(geminate).filter(char => geminate.filter(doudledChar => doudledChar === char).length > 3) || ["none"];
l < 20 ? "<span style='color:darkred'>[low]</span>" :
l < 25 ? "<span style='color:orange'>[low]</span>" : const duplicates = unique(namesArray.filter((e, i, a) => a.indexOf(e) !== i)).join(", ") || "none";
"<span style='color:green'>[good]</span>"; const multiwordRate = d3.mean(namesArray.map(n => +n.includes(" ")));
const getLengthQuality = () => {
if (length < 30) return "<span data-tip='Namesbase contains < 30 names - not enough to generate reasonable data' style='color:red'>[not enough]</span>";
if (length < 100) return "<span data-tip='Namesbase contains < 100 names - not enough to generate good names' style='color:darkred'>[low]</span>";
if (length <= 400) return "<span data-tip='Namesbase contains a reasonable number of samples' style='color:green'>[good]</span>";
return "<span data-tip='Namesbase contains > 400 names. That is too much, try to reduce it to ~300 names' style='color:darkred'>[overmuch]</span>";
};
const getVarietyLevel = () => {
if (variety < 15) return "<span data-tip='Namesbase average variety < 15 - generated names will be too repetitive' style='color:red'>[low]</span>";
if (variety < 30) return "<span data-tip='Namesbase average variety < 30 - names can be too repetitive' style='color:orange'>[mean]</span>";
return "<span data-tip='Namesbase variety is good' style='color:green'>[good]</span>";
};
alertMessage.innerHTML = `<div style="line-height: 1.6em; max-width: 20em"> alertMessage.innerHTML = `<div style="line-height: 1.6em; max-width: 20em">
<div>Namesbase length: ${l} ${lengthStat}</div> <div data-tip="Number of names provided">Namesbase length: ${length} ${getLengthQuality()}</div>
<div>Namesbase range: ${Object.keys(chain).length-1} ${rangeStat}</div> <div data-tip="Average number of generation variants for each key in the chain">Namesbase variety: ${variety} ${getVarietyLevel()}</div>
<div>Namesbase depth: ${depth} ${depthStat}</div>
<div>Non-basic chars: ${nonLatin}</div>
<hr> <hr>
<div>Min name length: ${d3.min(wordsLength)}</div> <div data-tip="The shortest name length">Min name length: ${d3.min(wordsLength)}</div>
<div>Max name length: ${d3.max(wordsLength)}</div> <div data-tip="The longest name length">Max name length: ${d3.max(wordsLength)}</div>
<div>Mean name length: ${rn(d3.mean(wordsLength), 1)}</div> <div data-tip="Average name length">Mean name length: ${rn(d3.mean(wordsLength), 1)}</div>
<div>Median name length: ${d3.median(wordsLength)}</div> <div data-tip="Common name length">Median name length: ${d3.median(wordsLength)}</div>
<div>Doubled chars: ${doubled}</div> <hr>
<div>Multi-word names: ${multi}%</div> <div data-tip="Characters outside of Basic Latin have bad font support">Non-basic chars: ${nonBasicLatinChars}</div>
<div data-tip="Characters that are frequently (more than 3 times) doubled">Doubled chars: ${doubled.join("")}</div>
<div data-tip="Names used more than one time">Duplicates: ${duplicates}</div>
<div data-tip="Percentage of names containing space character">Multi-word names: ${rn(multiwordRate * 100, 2)}%</div>
</div>`; </div>`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, title: "Data Analysis", resizable: false,
title: "Data Analysis",
position: {my: "left top-30", at: "right+10 top", of: "#namesbaseEditor"}, position: {my: "left top-30", at: "right+10 top", of: "#namesbaseEditor"},
buttons: {OK: function() {$(this).dialog("close");}} buttons: {
OK: function () {
$(this).dialog("close");
}
}
}); });
} }
@ -171,35 +197,42 @@ function editNamesbase() {
function namesbaseRestoreDefault() { function namesbaseRestoreDefault() {
alertMessage.innerHTML = `Are you sure you want to restore default namesbase?`; alertMessage.innerHTML = `Are you sure you want to restore default namesbase?`;
$("#alert").dialog({resizable: false, title: "Restore default data", $("#alert").dialog({
resizable: false,
title: "Restore default data",
buttons: { buttons: {
Restore: function() { Restore: function () {
$(this).dialog("close"); $(this).dialog("close");
Names.clearChains(); Names.clearChains();
nameBases = Names.getNameBases(); nameBases = Names.getNameBases();
createBasesList(); createBasesList();
updateInputs(); updateInputs();
}, },
Cancel: function() {$(this).dialog("close");} Cancel: function () {
$(this).dialog("close");
}
} }
}); });
} }
function namesbaseDownload() { function namesbaseDownload() {
const data = nameBases.map((b,i) => `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${b.b}`).join("\r\n"); const data = nameBases.map((b, i) => `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${b.b}`).join("\r\n");
const name = getFileName("Namesbase") + ".txt"; const name = getFileName("Namesbase") + ".txt";
downloadFile(data, name); downloadFile(data, name);
} }
function namesbaseUpload(dataLoaded) { function namesbaseUpload(dataLoaded) {
const data = dataLoaded.split("\r\n"); const data = dataLoaded.split("\r\n");
if (!data || !data[0]) {tip("Cannot load a namesbase. Please check the data format", false, "error"); return;} if (!data || !data[0]) {
tip("Cannot load a namesbase. Please check the data format", false, "error");
return;
}
Names.clearChains(); Names.clearChains();
nameBases = []; nameBases = [];
data.forEach(d => { data.forEach(d => {
const e = d.split("|"); const e = d.split("|");
nameBases.push({name:e[0], min:e[1], max:e[2], d:e[3], m:e[4], b:e[5]}); nameBases.push({name: e[0], min: e[1], max: e[2], d: e[3], m: e[4], b: e[5]});
}); });
createBasesList(); createBasesList();