-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTechNews.html
360 lines (314 loc) · 13.4 KB
/
TechNews.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
<html>
<head>
<title>Named Entity Visualization</title>
<style>
/* Default (Light) Theme */
:root {
--background-color: #ffffff;
--text-color: #000000;
--path-color: #000208;
--tooltip-background-color: #333333;
}
/* Dark Theme */
body.dark-theme {
--background-color: #1f1f1f;
--text-color: #ffffff;
--path-color: #9C27B0;
--tooltip-background-color: #444444;
}
body {
background-color: var(--background-color);
color: var(--text-color);
}
.entity-mark {
padding-right: 0.3em;
padding-left: 0.3em;
padding-top: 0.05em;
padding-bottom: 0.05em;
margin: 0 0.1em;
line-height: 1;
border-radius: 0.35em;
background: linear-gradient(#3f87a6, #ebf8e1, #f69d3c);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); /* Subtle shadow */
transition: background-color 0.3s ease, color 0.3s ease; /* Smooth transition */
}
.entity-mark:hover {
background-color: #b2ebf2; /* Slightly darker cyan on hover */
color: #004d40; /* Darker text on hover */
}
.custom-tooltip {
position: absolute;
background-color: var(--tooltip-background-color);
color: #fff;
padding: 8px; /* Slightly larger padding */
border-radius: 8px; /* More rounded corners */
z-index: 3;
white-space: nowrap;
pointer-events: none;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Subtle shadow */
transition: opacity 0.3s ease; /* Smooth transition for opacity */
opacity: 0.8; /* Slightly transparent */
}
#display-textbox {
width: 95%;
z-index: 1;
word-wrap: break-word; /* Break long words */
overflow-wrap: break-word; /* Ensure long words wrap */
white-space: normal; /* Allow normal word wrapping */
overflow-y: auto; /* Enable vertical scrolling if needed */
position: absolute;
line-height: 1.5;
}
#display-relation {
width: 95%;
position: absolute;
z-index: 2;
pointer-events: none; /* Make the SVG non-interactive */
}
.relation-path {
opacity: 0.6;
stroke: var(--path-color);
stroke-width: 2; /* Set the stroke width */
fill: none; /* Ensure the path is not filled */
}
#display-textbox-container {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="display-textbox-container">
<div id="display-textbox"></div>
<svg id="display-relation"></svg>
</div>
<script type="text/javascript">
// Embed the data as JavaScript variables
const data = {"text": "\nOn Monday, the tech giant, Innovia Corp, announced the launch of its new flagship smartphone, the Innovia XPro. The device, which comes in both 128GB and 256GB storage variants, is powered by the latest Octa-core Quantum processor and features a 6.5-inch AMOLED display. According to the CEO, Lisa Martin, this release marks a significant milestone for the company. The phone is expected to compete directly with the recent release from its rival, Nexon Technologies, which unveiled the Nexon Ultra series earlier this year.\n", "entities": [{"entity_id": "0", "start": 28, "end": 40, "attr": {"Type": "organization"}, "color": "#1f77b4"}, {"entity_id": "1", "start": 294, "end": 305, "attr": {"Type": "person", "Role": "CEO"}, "color": "#ff7f0e"}, {"entity_id": "2", "start": 99, "end": 111, "attr": {"Type": "product", "Specifications": "128GB, 256GB, Octa-core Quantum processor, 6.5-inch AMOLED display"}, "color": "#2ca02c"}, {"entity_id": "3", "start": 449, "end": 467, "attr": {"Type": "organization"}, "color": "#1f77b4"}, {"entity_id": "4", "start": 488, "end": 506, "attr": {"Type": "product"}, "color": "#2ca02c"}], "theme": "light", "relations": [{"entity_1_id": "0", "entity_2_id": "1"}, {"entity_1_id": "0", "entity_2_id": "2"}, {"entity_1_id": "3", "entity_2_id": "4"}]};
// Original JavaScript logic
function alineDisplayRelation(svg_padding_size) {
// Get the display-textbox (div) and display-relation (svg) elements
const textDiv = document.getElementById("display-textbox");
const svgContainer = document.getElementById("display-relation");
// Get dimensions of the display-textbox
const textDivRect = textDiv.getBoundingClientRect();
// Set dimensions of the SVG container
svgContainer.style.top = (textDivRect.top - svg_padding_size) + 'px';
svgContainer.style.left = (textDivRect.left - svg_padding_size) + 'px';
svgContainer.style.height = (textDivRect.height + 2*svg_padding_size) + 'px';
svgContainer.style.width = (textDivRect.width + 2*svg_padding_size) + 'px';
}
function updateEntities(text, entities) {
// Clear current display textbox
var textElement = document.getElementById("display-textbox");
textElement.innerHTML = '';
// If no entities
if (!entities || entities.length === 0) {
text.split('\n').forEach((line, index, array) => {
textElement.appendChild(document.createTextNode(line));
if (index < array.length - 1) {
textElement.appendChild(document.createElement("br"));
}
});
return null;
}
// Sort entities by start index
entities.forEach(ent => {
ent.start = parseInt(ent.start);
ent.end = parseInt(ent.end);
});
entities.sort((a, b) => a.start - b.start);
// Add entities to the display textbox
var lastIndex = 0;
entities.forEach(ent => {
// Add text before the entity
if (lastIndex < ent.start) {
var nonEntityText = text.substring(lastIndex, ent.start);
nonEntityText.split('\n').forEach((line, index, array) => {
textElement.appendChild(document.createTextNode(line));
if (index < array.length - 1) {
textElement.appendChild(document.createElement("br"));
}
});
}
// Add the entity text
var entityText = text.substring(ent.start, ent.end);
var entityElement = document.createElement("mark");
entityElement.id = ent.entity_id;
entityElement.className = "entity-mark";
entityElement.textContent = entityText;
if (ent.color) {
entityElement.style.background = 'none';
entityElement.style.backgroundColor = ent.color;
}
entityElement.addEventListener('mouseenter', function(event) {
if (ent.attr) {
const prettyAttributes = JSON.stringify(ent.attr, null, 2).replace(/\n/g, '<br>').replace(/ /g, ' ');
showTooltip(event, `Entity ID: ${ent.entity_id}<br>Text: ${entityText}<br>Attributes: ${prettyAttributes}`);
}
else {
showTooltip(event, `Entity ID: ${ent.entity_id}<br>Text: ${entityText}`);
}
});
entityElement.addEventListener('mouseleave', function() {
hideTooltip();
});
textElement.appendChild(entityElement);
lastIndex = ent.end;
});
// Add remaining text after the last entity
if (lastIndex < text.length) {
var remainingText = text.substring(lastIndex);
remainingText.split('\n').forEach((line, index, array) => {
textElement.appendChild(document.createTextNode(line));
if (index < array.length - 1) {
textElement.appendChild(document.createElement("br"));
}
});
}
}
function showTooltip(event, content) {
// Remove any existing tooltip
var existingTooltip = document.getElementById("custom-tooltip");
if (existingTooltip) {
existingTooltip.remove();
}
// Create a new tooltip
var tooltip = document.createElement("div");
tooltip.id = "custom-tooltip";
tooltip.className = "custom-tooltip";
tooltip.innerHTML = content;
document.body.appendChild(tooltip);
// Position the tooltip
var rect = event.target.getBoundingClientRect();
var x = rect.left + window.scrollX;
// 15px above the entity
var y = rect.top + window.scrollY - tooltip.offsetHeight - 15;
// Ensure the tooltip doesn't go off-screen
if (x + tooltip.offsetWidth > window.innerWidth) {
// 10px padding from the right edge
x = window.innerWidth - tooltip.offsetWidth - 10;
}
if (y < 0) {
// 5px below the entity if it goes off-screen
y = rect.bottom + window.scrollY + 5;
}
tooltip.style.left = x + "px";
tooltip.style.top = y + "px";
}
function hideTooltip() {
var tooltip = document.getElementById("custom-tooltip");
if (tooltip) {
tooltip.remove();
}
}
function updateRelations(relations, r, svg_padding_size) {
/*
* Update the relation lines between entities
*
* relations: list of relation objects with entity_1_id and entity_2_id
* r: level size for the relation paths
* svg_padding_size: padding size for the svg container
*/
// If no relations
if (!relations || relations.length === 0) {
return null;
}
// Get the display-textbox (div) and display-relation (svg) elements
const textDiv = document.getElementById("display-textbox");
const svgContainer = document.getElementById("display-relation");
const textDivRect = textDiv.getBoundingClientRect();
// Clear current relation lines
svgContainer.innerHTML = '';
relations.forEach(rel => {
const entity1Element = document.getElementById(rel.entity_1_id);
const entity2Element = document.getElementById(rel.entity_2_id);
// Check if elements exist
if (!entity1Element || !entity2Element) {
console.error('Entity elements not found:', rel.entity_1_id, rel.entity_2_id);
return;
}
const entity1Rect = entity1Element.getBoundingClientRect();
const entity2Rect = entity2Element.getBoundingClientRect();
// Get the computed style of the entity elements to determine the line height
const entity1Style = window.getComputedStyle(entity1Element);
const entity2Style = window.getComputedStyle(entity2Element);
const entity1LineHeight = parseFloat(entity1Style.lineHeight);
const entity2LineHeight = parseFloat(entity2Style.lineHeight);
// Calculate the number of lines
const entity1Lines = entity1Rect.height / entity1LineHeight;
const entity2Lines = entity2Rect.height / entity2LineHeight;
// Handle line-broken entity
// When an entity is split by line change, we use the top-left as StartX
const entity1X = entity1Lines > 2 ? entity1Rect.right - textDivRect.left + svg_padding_size : entity1Rect.left + entity1Rect.width / 2 - textDivRect.left + svg_padding_size;
const entity1Y = entity1Rect.top - textDivRect.top + svg_padding_size;
const entity2X = entity2Lines > 2 ? entity2Rect.right - textDivRect.left + svg_padding_size : entity2Rect.left + entity2Rect.width / 2 - textDivRect.left + svg_padding_size;
const entity2Y = entity2Rect.top - textDivRect.top + svg_padding_size;
// start with entity on left
var startX = null;
var startY = null;
var endX = null;
var endY = null;
if (entity1X < entity2X) {
startX = entity1X;
startY = entity1Y;
endX = entity2X;
endY = entity2Y;
} else {
startX = entity2X;
startY = entity2Y;
endX = entity1X;
endY = entity1Y;
}
// Create the SVG path
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
// If start and end entities are on the same y-axis
if (startY === endY) {
path.setAttribute("d", `M${startX} ${startY} a ${r} ${r} 0 0 1 ${r} -${r} L${endX - r} ${startY - r} a ${r} ${r} 0 0 1 ${r} ${r} L${endX} ${endY}`);
}
// If start entity is below end entities
else if (startY > endY) {
path.setAttribute("d", `M${startX} ${startY} L${startX} ${endY} a ${r} ${r} 0 0 1 ${r} -${r} L${endX - r} ${endY - r} a ${r} ${r} 0 0 1 ${r} ${r} L${endX} ${endY}`);
}
// If start entity is above end entities
else {
path.setAttribute("d", `M${startX} ${startY} a ${r} ${r} 0 0 1 ${r} -${r} L${endX - r} ${startY - r} a ${r} ${r} 0 0 1 ${r} ${r} L${endX} ${endY}`);
}
path.setAttribute("class", "relation-path");
svgContainer.appendChild(path);
});
}
// Global variables for relation lines
let relations = null;
let relationPathLevel = 5;
let svgPaddingSize = 20;
// This function wait until an element is rendered
const checkElement = async selector => {
while ( document.getElementById(selector) === null) {
await new Promise( resolve => requestAnimationFrame(resolve) )
}
return document.getElementById(selector);
};
updateEntities(data.text, data.entities);
if ('theme' in data) {
if (data.theme === 'dark') {
document.body.className = '';
document.body.classList.add('dark-theme');
}
}
if ('relations' in data) {
relations = data.relations;
alineDisplayRelation(svgPaddingSize);
updateRelations(relations, relationPathLevel, svgPaddingSize);
// Add event listener for the main page scroll
window.addEventListener('scroll', () => {
updateRelations(relations, relationPathLevel, svgPaddingSize);
});
// Add event listener for the main page resize
window.addEventListener('resize', () => {
updateRelations(relations, relationPathLevel, svgPaddingSize);
});
}
</script>
</body>
</html>