- beautify layout

- restructure code
- restructure workflow
- actually write back changes to the internal data structure
- add functionality that can actually save back all changes to a file on
disk
- add navigation functionality such that even big files can be edited
pull/39/head
Kai Labusch 5 years ago
parent 8488ce1afd
commit 3204bc5869

@ -1,2 +1,5 @@
# ner.edith
![Screenshot](https://github.com/cneud/ner.edith/blob/master/screenshot.png)
- use mouse wheel to scroll up and down
- use navigation << and >> to move faster

@ -0,0 +1,272 @@
var data;
var file = null;
var displayRows=30
var startIndex=0;
var endIndex=displayRows;
function loadFile(evt) {
let table_html =
`
<table id="table">
<thead>
<tr>
<th><button class="btn btn-link" id="back"><<</button>OFFSET</th>
<th>POSITION</th>
<th>TOKEN</th>
<th>NE-TAG</th>
<th>NE-EMB<button class="btn btn-link" id="next">>></button></th>
</tr>
</thead>
<tbody id="table-body"></tbody>
</table>
<br/>
<br/>
`;
let save_html =
`<button class="btn btn-primary saveButton">Save Changes</button>`
$("#tableregion").html(table_html)
$("#btn-region").html(save_html)
$('.saveButton').on('click', saveFile)
let editingTd;
function finishTdEdit(td, isOk) {
if (isOk) {
let newValue = $('#edit-area').val();
$(td).html(newValue);
let tableInfo = $(td).data('tableInfo');
data.data[tableInfo.nRow][tableInfo.column] = newValue;
}
else {
$(td).html(editingTd.data);
}
editingTd = null;
}
function makeTdEditable(td) {
editingTd = {
elem: td,
data: td.innerHTML
};
let textArea = document.createElement('textarea');
textArea.style.width = td.clientWidth + 'px';
textArea.style.height = td.clientHeight + 'px';
textArea.id = 'edit-area';
$(textArea).val($(td).html());
$(td).html('');
$(td).append(textArea);
textArea.focus();
let edit_html =
`<div class="edit-controls">
<button class="btn btn-secondary btn-sm" id="edit-ok">OK</button>
<button class="btn btn-secondary btn-sm" id="edit-cancel">CANCEL</button>
</div>`
td.insertAdjacentHTML("beforeEnd", edit_html);
$('#edit-ok').on('click',
function(evt) {
finishTdEdit(editingTd.elem, true);
});
$('#edit-cancel').on('click',
function(evt) {
finishTdEdit(editingTd.elem, false);
});
}
$('#table').on('click',
function(event) {
let target = event.target.closest('.editable');
if (editingTd) {
if (target == editingTd.elem) return;
finishTdEdit(editingTd.elem, true);
}
if (!$.contains($('#table')[0], target)) return
makeTdEditable(target);
});
file = evt.target.files[0];
// TODO: adapt to streaming with 'chunk' callback for large file support, see https://www.papaparse.com/docs
Papa.parse(file, {
header: true,
delimiter: '\t',
comments: "#",
skipEmptyLines: true,
dynamicTyping: true,
complete: function(results) {
//console.log(results);
data = results;
updateTable();
$("#file-region").html('<h3>' + file.name + '</h3>');
$('#tableregion')[0].addEventListener("wheel",
function(event) {
if (event.deltaY < 0) {
if (startIndex <= 0) return;
startIndex -= 1;
endIndex -= 1;
}
else {
if (endIndex >= data.data.length) return;
startIndex += 1;
endIndex += 1;
}
updateTable();
});
$('#back').on('click',
function(evt) {
if (startIndex >= displayRows) {
startIndex -= displayRows;
endIndex -= displayRows;
}
else {
startIndex = 0;
endIndex = displayRows;
}
updateTable();
}
);
$('#next').on('click',
function(evt) {
if (endIndex + displayRows < data.data.length) {
endIndex += displayRows;
startIndex = endIndex - displayRows;
}
else {
endIndex = data.data.length;
startIndex = endIndex - displayRows;
}
updateTable();
}
);
}
});
}
function updateTable() {
let editable_html =
`
<td class="editable">
`;
$('#table-body').empty();
$.each(data.data,
function(nRow, el) {
if (nRow < startIndex) return;
if (nRow >= endIndex) return;
var row = $("<tr/>");
row.append($("<td/>").text(nRow));
$.each(el,
function(column, content) {
row.append(
$(editable_html).
text(content).
data('tableInfo', { 'nRow': nRow, 'column': column })
);
});
$("#table tbody").append(row);
});
$("#table td:contains('B-PER')").addClass('ner_per');
$("#table td:contains('I-PER')").addClass('ner_per');
$("#table td:contains('B-LOC')").addClass('ner_loc');
$("#table td:contains('I-LOC')").addClass('ner_loc');
$("#table td:contains('B-ORG')").addClass('ner_org');
$("#table td:contains('I-ORG')").addClass('ner_org');
$("#table td:contains('B-OTH')").addClass('ner_oth');
$("#table td:contains('I-OTH')").addClass('ner_oth');
}
function saveFile(evt) {
let csv =
Papa.unparse(data,
{
header: true,
delimiter: '\t',
comments: "#",
skipEmptyLines: true,
dynamicTyping: true
});
openSaveFileDialog (csv, file.name, null)
}
function openSaveFileDialog (data, filename, mimetype) {
if (!data) return;
var blob = data.constructor !== Blob
? new Blob([data], {type: mimetype || 'application/octet-stream'})
: data ;
if (navigator.msSaveBlob) {
navigator.msSaveBlob(blob, filename);
return;
}
var lnk = document.createElement('a'),
url = window.URL,
objectURL;
if (mimetype) {
lnk.type = mimetype;
}
lnk.download = filename || 'untitled';
lnk.href = objectURL = url.createObjectURL(blob);
lnk.dispatchEvent(new MouseEvent('click'));
setTimeout(url.revokeObjectURL.bind(url, objectURL));
}
$(document).ready(
function() {
$('#tsv-file').change(loadFile);
}
);

@ -3,130 +3,66 @@
<head>
<meta charset="UTF-8">
<title>ner.edith</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.0.1/papaparse.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<style>
body{font-family:Open Sans;font-size:16px}
table{table-layout:fixed;width:70%;text-align:center}
table{table-layout:fixed;width:100%;text-align:center}
th{background-color:lightgray}
td:hover{background-color:yellow}
.editable:hover{background-color:yellow}
tr:hover{background-color:whitesmoke}
.ner_per{background-color:skyblue}
.ner_loc{background-color:goldenrod}
.ner_org{background-color:plum}
.ner_oth{background-color:lightgreen}
</style>
</head>
<body>
<h3>ner.edith: named entity recognition editor in html</h3>
Please upload a TSV (tab-separated-values) file in the GermEval2014 data format<sup>[<a href="https://sites.google.com/site/germeval2014ner/data" target="_blank">i</a>]</sup>: <input type="file" id="tsv-file" name="files"/><br><br>
<table id="table">
<thead>
<tr>
<th>OFFSET</th>
<th>POSITION</th>
<th>TOKEN</th>
<th>NE-TAG</th>
<th>NE-EMB</th>
</tr>
</thead>
<tbody></tbody>
</table>
<script>
var data;
function handleFileSelect(evt) {
var file = evt.target.files[0];
// TODO: adapt to streaming with 'chunk' callback for large file support, see https://www.papaparse.com/docs
Papa.parse(file, {
header: true,
delimiter: '\t',
comments: "#",
skipEmptyLines: true,
dynamicTyping: true,
complete: function(results) {
console.log(results);
data = results;
$.each(results.data, function(i, el) {
var row = $("<tr/>");
row.append($("<td/>").text(i));
$.each(el, function(i, cell) {
row.append($("<td/>").text(cell));
});
$("#table tbody").append(row);
$("#table td:contains('B-PER')").addClass('ner_per');
$("#table td:contains('I-PER')").addClass('ner_per');
$("#table td:contains('B-LOC')").addClass('ner_loc');
$("#table td:contains('I-LOC')").addClass('ner_loc');
$("#table td:contains('B-ORG')").addClass('ner_org');
$("#table td:contains('I-ORG')").addClass('ner_org');
$("#table td:contains('B-OTH')").addClass('ner_oth');
$("#table td:contains('I-OTH')").addClass('ner_oth');
});
}
});
}
// https://javascript.info/task/edit-td-click
let table = document.getElementById('table');
let editingTd;
table.onclick = function(event) {
let target = event.target.closest('.edit-cancel,.edit-ok,td');
if (!table.contains(target)) return;
if (target.className == 'edit-cancel') {
finishTdEdit(editingTd.elem, false);
} else if (target.className == 'edit-ok') {
finishTdEdit(editingTd.elem, true);
} else if (target.nodeName == 'TD') {
if (editingTd) return;
makeTdEditable(target);
}
}
function makeTdEditable(td) {
editingTd = {
elem: td,
data: td.innerHTML
};
td.classList.add('edit-td');
let textArea = document.createElement('textarea');
textArea.style.width = td.clientWidth + 'px';
textArea.style.height = td.clientHeight + 'px';
textArea.className = 'edit-area';
textArea.value = td.innerHTML;
td.innerHTML = '';
td.appendChild(textArea);
textArea.focus();
td.insertAdjacentHTML("beforeEnd",
'<div class="edit-controls"><button class="edit-ok">OK</button><button class="edit-cancel">CANCEL</button></div>'
);
}
function finishTdEdit(td, isOk) {
if (isOk) {
td.innerHTML = td.firstChild.value;
} else {
td.innerHTML = editingTd.data;
}
td.classList.remove('edit-td');
editingTd = null;
}
$(document).ready(function() {
$('#tsv-file').change(handleFileSelect);
});
</script>
<div class="container-fluid">
<div class="row mt-5">
<div class="col-2">
<div id="file-region"></div>
</div>
<div class="col-8">
<div class="row">
<div class="col text-center">
<h1>ner.edith: <br/> named entity recognition editor in html</h1>
</div>
</div>
</div>
<div class="col-2">
</div>
</div>
<div class="row mt-3">
<div class="col-2">
</div>
<div class="col-8 text-center" id="tableregion">
Please upload a TSV (tab-separated-values) file in the GermEval2014 data format
<sup>[<a href="https://sites.google.com/site/germeval2014ner/data" target="_blank">i</a>]</sup>:
<br>
<br><br><br><br>
<input type="file" id="tsv-file" name="files"/><br><br>
</div>
<div class="col-2" id="btnregion">
</div>
</div>
<div class="row mt-3">
<div class="col-2">
</div>
<div class="col-8 text-center" id="btn-region">
</div>
<div class="col-2">
</div>
</div>
</div>
<script src="ner-edith.js"></script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Loading…
Cancel
Save