前面作者讲解了很多知识图谱原理知识,包括知识图谱相关技术、Neo4j绘制关系图谱等,但仍缺少一个系统全面的实例。为了加深自己对知识图谱构建的认识,为后续创建贵州旅游知识图谱打下基础,作者深入学习了张宏伦老师的网易云课程(星球系列电影),并结合自己的理解和技术分享了该系列专栏,从数据采集、数据展示、数据分析到知识图谱构建,文章后续还会讲解中文数据的实体识别、关系抽取、知识计算等。
前面通过六篇文章基本构建了电影知识图谱,并且能显示选中节点相关联的边及属性,如下图所示:
本文主要增加了一个搜索功能,通过该搜索框能展示搜索节点的相关内容,同时在张老师的基础上增加搜索节点相关联的边及节点。如下图所示:
代码下载地址:https://download.csdn.net/download/eastmount/10958879
这是一系列基础性文章,希望对您有所帮助 ,尤其是对知识图谱感兴趣和编程刚入门的同学。同时也因为最近准备博士考试,做题做吐了,写点新专栏调节下心情,与君共勉,一起加油。
前文:
[知识图谱实战篇] 一.数据抓取之Python3抓取JSON格式的电影实体
[知识图谱实战篇] 二.Json+Seaborn可视化展示电影实体
[知识图谱实战篇] 三.Python提取JSON数据、HTML+D3构建基本可视化布局
[知识图谱实战篇] 四.HTML+D3+CSS绘制关系图谱
[知识图谱实战篇] 五.HTML+D3添加鼠标响应事件显示相关节点及边
[知识图谱实战篇] 六.HTML+D3实现点击节点显示相关属性及属性值
推荐作者的知识图谱前文:
知识图谱相关会议之观后感分享与学习总结
中文知识图谱研讨会的学习总结 (上) 图谱引入、百度知心、搜狗知立方
搜索引擎和知识图谱那些事 (上).基础篇
基于VSM的命名实体识别、歧义消解和指代消解
CSDN下载-第一届全国中文知识图谱研讨会演讲PPT 清华大学
CSDN下载-知识图谱PDF资料 清华大学知识图谱研讨会汇报PPT
[知识图谱构建] 一.Neo4j图数据库安装初识及药材供应图谱实例
[知识图谱构建] 二.《Neo4j基础入门》基础学习之创建图数据库节点及关系
[关系图谱] 一.Gephi通过共线矩阵构建知网作者关系图谱
[关系图谱] 二.Gephi导入共线矩阵构建作者关系图谱
再次强烈推荐大家阅读张宏伦老师的网易云课程及Github源码,受益匪浅。
https://github.com/Honlan/starwar-visualization/tree/master/star_war
https://study.163.com/course/courseLearn.htm?courseId=1003528010
首先,利用HTML绘制一个搜索框。
<div id="search">
<input type="text" class="form-control">
div>
注意,class为form-control,它是bootstrap的基本样式,所以需要在前面导入基本样式代码,如下:
<link href="http://cdn.bootcss.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">
<script src="http://cdn.bootcss.com/bootstrap/3.3.4/js/bootstrap.min.js">script>
此时,你会发现新增的搜索框已经被置于底部,接着需要通过CSS设置其样式。
CSS增加样式代码如下:绝对定位、位置、颜色、边框去掉、outline去掉、阴影 去掉、跨度设置、背景颜色设置。
#search input {
position: absolute;
top: 220px;
left: 60px;
color: #fff;
border: none;
outline: none;
box-shadow: none;
width: 200px;
background-color: #666;
}
此时就增加好了搜索框:
当搜索框设置完成之后,再设置其响应事件。添加事件 $(’#search1 input’).keyup(function(event) {})
其基本功能表示:
<script type="text/javascript">
//搜索框中输入内容则响应该事件
//keyup按键敲击响应event
$('#search input').keyup(function(event) {
//如果Input值是空的显示所有的圆和线(没有进行筛选)
if ($(this).val() == '') {
d3.select('#svg1 .texts').selectAll('text').attr('class', '');
d3.select('#svg1 .nodes').selectAll('circle').attr('class', '');
d3.select('#svg1 .links').selectAll('line').attr('class', '');
}
//否则判断判断三个元素是否等于name值,等于则显示该值
else {
var name = $(this).val();
//搜索所有的节点
d3.select('#svg1 .nodes').selectAll('circle').attr('class', function(d) {
//输入节点id的小写等于name则显示,否则隐藏
if (d.id.toLowerCase().indexOf(name.toLowerCase()) >= 0) {
return '';
} else {
return 'inactive'; //隐藏
}
});
//搜索texts
d3.select('#svg1 .texts').selectAll('text').attr('class', function(d) {
if (d.id.toLowerCase().indexOf(name.toLowerCase()) >= 0) {
return '';
} else {
return 'inactive';
}
});
//搜索links
d3.select("#svg1 .links").selectAll('line').attr('class', function(d) {
//return 'inactive';
});
}
});
script>
此时的运行效果如下图所示,比如搜索 luke 则反馈该节点,它是一个人物。
点击“文字”的搜索也是如此,仅显示单一节点。
但我们更期待的结果是反馈搜索节点及其相关联的边及节点,下面作者结合之前的文章进行优化。此时原理很简单,增加一个循环,判断所有边的起点(Source)或终点(Target)与该搜索节点相邻,则显示,否则设置其class属性为’inactive’,即隐藏节点。
核心代码如下:
<script>
//搜索框中输入内容则响应该事件
//keyup按键敲击响应event
$('#search input').keyup(function(event) {
//如果Input值是空的显示所有的圆和线(没有进行筛选)
if ($(this).val() == '') {
d3.select('#svg1 .texts').selectAll('text').attr('class', '');
d3.select('#svg1 .nodes').selectAll('circle').attr('class', '');
d3.select('#svg1 .links').selectAll('line').attr('class', '');
}
//否则判断判断三个元素是否等于name值,等于则显示该值
else {
var name = $(this).val();
//搜索所有的节点
d3.select('#svg1 .nodes').selectAll('circle').attr('class', function(d) {
//输入节点id的小写等于name则显示,否则隐藏
if (d.id.toLowerCase().indexOf(name.toLowerCase()) >= 0) {
return '';
} else {
//优化:与该搜索节点相关联的节点均显示
//links链接的起始节点进行判断,如果其id等于name则显示这类节点
//注意: graph=data
for (var i = 0; i < graph.links.length; i++) {
//如果links的起点等于name,并且终点等于正在处理的则显示
if ((graph.links[i]['source'].id.toLowerCase().indexOf(name.toLowerCase()) >= 0) &&
(graph.links[i]['target'].id == d.id)) {
return '';
}
//如果links的终点等于name,并且起点等于正在处理的则显示
if ((graph.links[i]['target'].id.toLowerCase().indexOf(name.toLowerCase()) >= 0) &&
(graph.links[i]['source'].id == d.id)) {
return '';
}
}
return 'inactive'; //隐藏其他节点
}
});
//搜索texts
d3.select('#svg1 .texts').selectAll('text').attr('class', function(d) {
if (d.id.toLowerCase().indexOf(name.toLowerCase()) >= 0) {
return '';
} else {
//优化:与该搜索节点相关联的节点均显示
//links链接的起始节点进行判断,如果其id等于name则显示这类节点
//注意: graph=data
for (var i = 0; i < graph.links.length; i++) {
//如果links的起点等于name,并且终点等于正在处理的则显示
if ((graph.links[i]['source'].id.toLowerCase().indexOf(name.toLowerCase()) >= 0) &&
(graph.links[i]['target'].id == d.id)) {
return '';
}
//如果links的终点等于name,并且起点等于正在处理的则显示
if ((graph.links[i]['target'].id.toLowerCase().indexOf(name.toLowerCase()) >= 0) &&
(graph.links[i]['source'].id == d.id)) {
return '';
}
}
return 'inactive'; //隐藏其他节点
}
});
//搜索links 所有与搜索name相关联的边均显示
//显示相的邻边 注意 ||
//name=$(this).val():名字为键盘输入的内容
d3.select("#svg1 .links").selectAll('line').attr('class', function(d) {
if ((d.source.id.toLowerCase().indexOf(name.toLowerCase()) >= 0) ||
(d.target.id.toLowerCase().indexOf(name.toLowerCase()) >= 0)
) {
return '';
} else {
return 'inactive'; //隐藏
}
});
}
}); //end input
script>
运行结果如下图所示,同时如果有多个名字相关的节点,它们与其关联的节点和边均会显示。
PS:但是也存在一个问题,即右边显示的属性为鼠标选中显示,而搜索通常不能显示属性,但这不会影响整体效果。
完整代码如下所示:
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>知识图谱title>
<meta name="description" content="" />
<meta name="keywords" content="" />
<meta name="author" content="" />
<link rel="shortcut icon" href="">
<script src="http://cdn.bootcss.com/jquery/2.1.4/jquery.min.js">script>
<link href="http://cdn.bootcss.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">
<script src="http://cdn.bootcss.com/bootstrap/3.3.4/js/bootstrap.min.js">script>
head>
<style type="text/css">
body {
background-color: #272b30;
padding: 30px 40px;
text-align: center;
font-family: OpenSans-Light, PingFang SC, Hiragino Sans GB, Microsoft Yahei, Microsoft Jhenghei, sans-serif;
}
#indicator {
position: absolute;
left: 60px;
bottom: 120px;
text-align: left;
color: #f2f2f2;
font-size: 12px;
}
#indicator>div {
margin-bottom: 4px;
}
#indicator span {
display: inline-block;
width: 30px;
height: 14px;
position: relative;
top: 2px;
margin-right: 8px;
}
.links line {
stroke: rgb(240, 240, 240);
stroke-opactity: 0.2;
}
.links line.inactive {
/*display: none !important;*/
stroke-opacity: 0;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
.nodes circle:hover {
cursor: pointer;
}
.nodes circle.inactive {
display: none !important;
}
.texts text {
display: none;
}
.texts text:hover {
cursor: pointer;
}
.texts text.inactive {
display: none !important;
}
#mode {
position: absolute;
top: 160px;
left: 60px;
}
#mode span {
display: inline-block;
border: 1px solid #fff;
color: #fff;
padding: 6px 10px;
border-radius: 4px;
font-size: 14px;
transition: color, background-color .3s;
-o-transition: color, background-color .3s;
-ms-transition: color, background-color .3s;
-moz-transition: color, background-color .3s;
-webkit-transition: color, background-color .3s;
}
#mode span.active, #mode span:hover {
background-color: #fff;
color: #333;
cursor: pointer;
}
#info {
position: absolute;
bottom: 40px;
right: 30px;
text-align: right;
width: 270px;
}
#info p {
color: #fff;
font-size: 12px;
margin-top: 0;
margin-bottom: 5px;
}
#info p span {
color: #888;
margin-right: 10px;
}
#info h4 {
color: #fff;
}
#search input {
position: absolute;
top: 220px;
left: 60px;
color: #fff;
border: none;
outline: none;
box-shadow: none;
width: 200px;
background-color: #666;
}
style>
<body>
<h1 style="color:#fff;font-size:32px;margin-bottom:0px;text-align:center;margin-left:40px;">Star Warsh1>
<div style="text-align: center; position:relative;">
<svg width="800" height="560" style="margin-right:80px;margin-bottom:-40px;" id="svg1">
svg>
<div id="indicator">
div>
<div id="mode">
<span class="active" style="border-top-right-radius:0;border-bottom-right-radius:0;">节点span>
<span style="border-top-left-radius:0;border-bottom-left-radius:0;position:relative;left:-5px;">文字span>
div>
<div id="search">
<input type="text" class="form-control">
div>
<div id="info">
<h4>h4>
div>
div>
<div style="text-align: center; position:relative;">
<svg width="960" height="240" style="margin-right:60px;margin-bottom:-40px;" id="svg1">
<g>g>
svg>
div>
body>
<script src="https://d3js.org/d3.v4.min.js">script>
<script type="text/javascript">
$(document).ready(function() {
//定义svg变量将布局svg1选出来
var svg = d3.select("#svg1"),
width = svg.attr("width"),
height = svg.attr("height");
//定义name变量制作图标
var names = ['Films', 'Characters', 'Planets', 'Starships', 'Vehicles', 'Species'];
var colors = ['#6ca46c', '#4e88af', '#ca635f', '#d2907c', '#d6744d', '#ded295'];
//背景颜色设置 补充CSS样式设置字体布局
for (var i=0; i < names.length; i++) {
$('#indicator').append("" + names[i] + "");
}
//利用d3.forceSimulation()定义关系图 包括设置边link、排斥电荷charge、关系图中心点
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
//存储关系图的数据
var graph;
//定义d3.json请求python处理好的节点及边 请求成功返回数据,否则报错
d3.json("starwar_alldata.json", function(error, data) {
if(error) throw error;
graph = data;
console.log(graph);
//D3映射数据至HTML中
//g用于绘制所有边,selectALL选中所有的line,并绑定数据data(graph.links),enter().append("line")添加元素
//数据驱动文档,设置边的粗细
//前面定义var svg = d3.select("#svg1")
var link = svg.append("g").attr("class","links").selectAll("line").data(graph.links)
.enter().append("line").attr("stroke-width", function(d) {
//return Math.sqrt(d.value);
return 1; //所有线宽度均为1
});
//添加所有的点
//selectAll("circle")选中所有的圆并绑定数据,圆的直径为d.size
//再定义圆的填充色,同样数据驱动样式,圆没有描边,圆的名字为d.id
//call()函数:拖动函数,当拖动开始绑定dragstarted函数,拖动进行和拖动结束也绑定函数
var node = svg.append("g").attr("class", "nodes").selectAll("circle").data(graph.nodes)
.enter().append("circle").attr("r", function(d) {
return d.size;
}).attr("fill", function(d) {
return colors[d.group];
}).attr("stroke", "none").attr("name", function(d) {
return d.id;
}).call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
//显示所有的文本
//设置大小、填充颜色、名字、text()设置文本
//attr("text-anchor", "middle")文本居中
var text = svg.append("g").attr("class", "texts").selectAll("text").data(graph.nodes)
.enter().append("text").attr("font-size", function(d) {
return d.size;
}).attr("fill", function(d) {
return colors[d.group];
}).attr('name', function(d) {
return d.id;
}).text(function(d) {
return d.id;
}).attr('text-anchor', 'middle').call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
//圆增加title
node.append("title").text(function(d) {
return d.id;
})
//simulation中ticked数据初始化,并生成图形
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
//ticked()函数确定link线的起始点x、y坐标 node确定中心点 文本通过translate平移变化
function ticked() {
link
.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
text.
attr('transform', function(d) {
return 'translate(' + d.x + ',' + (d.y + d.size / 2) + ')';
});
}
});
// Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension
// 本地json数据需要放置服务器中请求 XAMPP
//该变量保证拖动鼠标时,不会影响图形变换,默认为false未选中鼠标
var dragging = false;
//开始拖动并更新相应的点
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
dragging = true;
}
//拖动进行中
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
//拖动结束
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
dragging = false;
}
//span点击事件
$('#mode span').click(function(event) {
//span都设置为不激活状态
$('#mode span').removeClass('active');
//点击的span被激活
$(this).addClass('active');
//text隐藏 nodes显示
if ($(this).text() == '节点') {
$('.texts text').hide();
$('.nodes circle').show();
} else {
$('.texts text').show();
$('.nodes circle').hide();
}
});
//为svg1父元素下的.nodes circle元素绑定鼠标进入事件
$('#svg1').on('mouseenter', '.nodes circle', function(event) {
//通过变量dragging保证拖动鼠标时,其状态不受影响,从而改变图形
//鼠标没有拖动才能处理事件
if(!dragging) {
//获取被选中元素的名字
var name = $(this).attr("name");
//设置#info h4样式的颜色为该节点的颜色,文本为该节点name
//$(this).attr('fill')表示当前悬浮圆的填充色
$('#info h4').css('color', $(this).attr('fill')).text(name);
//每次点击添加属性前把上次显示的信息去除,否则会不断叠加
$('#info p').remove();
//打印悬浮的节点信息
//console.log(info[name]);
//遍历所有的
for (var key in info[name]) {
//类型复杂的不进行显示
if (typeof(info[name][key]) == 'object') {
continue;
}
//比较复杂的超链接字段不显示
if (key == 'url' || key == 'title' || key == 'name' ||
key == 'edited' || key == 'created' || key == 'homeworld') {
continue;
}
//显示值及其字段名字
$('#info').append('' + key + ''
+ info[name][key] + '');
}
//选择#svg1 .nodes中所有的circle,再增加个class
d3.select('#svg1 .nodes').selectAll('circle').attr('class', function(d) {
//数据的id是否等于name,返回空
if(d.id==name) {
return '';
}
//当前节点返回空,否则其他节点循环判断是否被隐藏起来(CSS设置隐藏)
else {
//links链接的起始节点进行判断,如果其id等于name则显示这类节点
//注意: graph=data
for (var i = 0; i < graph.links.length; i++) {
//如果links的起点等于name,并且终点等于正在处理的则显示
if (graph.links[i]['source'].id == name && graph.links[i]['target'].id == d.id) {
return '';
}
if (graph.links[i]['target'].id == name && graph.links[i]['source'].id == d.id) {
return '';
}
}
return "inactive"; //前面CSS定义 .nodes circle.inactive
}
});
//处理相邻的边line是否隐藏 注意 ||
d3.select("#svg1 .links").selectAll('line').attr('class', function(d) {
if (d.source.id == name || d.target.id == name) {
return '';
} else {
return 'inactive';
}
});
}
});
//鼠标移开还原原图,显示所有隐藏的点及边
$('#svg1').on('mouseleave', '.nodes circle', function(event) {
//如果dragging为false才处理事件
if(!dragging) {
d3.select('#svg1 .nodes').selectAll('circle').attr('class', '');
d3.select('#svg1 .links').selectAll('line').attr('class', '');
}
});
//鼠标进入文本显示相邻节点及边
$('#svg1').on('mouseenter', '.texts text', function(event) {
if (!dragging) {
var name = $(this).attr('name');
//同样的代码从选中圆中赋值过来
$('#info h4').css('color', $(this).attr('fill')).text(name);
$('#info p').remove();
for (var key in info[name]) {
if (typeof(info[name][key]) == 'object') {
continue;
}
if (key == 'url' || key == 'title' || key == 'name' || key == 'edited' || key == 'created' || key == 'homeworld') {
continue;
}
$('#info').append('' + key + ''
+ info[name][key] + '');
}
d3.select('#svg1 .texts').selectAll('text').attr('class', function(d) {
if (d.id == name) {
return '';
}
for (var i = 0; i < graph.links.length; i++) {
if (graph.links[i]['source'].id == name && graph.links[i]['target'].id == d.id) {
return '';
}
if (graph.links[i]['target'].id == name && graph.links[i]['source'].id == d.id) {
return '';
}
}
return 'inactive';
});
d3.select("#svg1 .links").selectAll('line').attr('class', function(d) {
if (d.source.id == name || d.target.id == name) {
return '';
} else {
return 'inactive';
}
});
}
});
//鼠标移除文本还原相应节点及边
$('#svg1').on('mouseleave', '.texts text', function(event) {
if (!dragging) {
d3.select('#svg1 .texts').selectAll('text').attr('class', '');
d3.select('#svg1 .links').selectAll('line').attr('class', '');
}
});
//搜索框中输入内容则响应该事件
//keyup按键敲击响应event
$('#search input').keyup(function(event) {
//如果Input值是空的显示所有的圆和线(没有进行筛选)
if ($(this).val() == '') {
d3.select('#svg1 .texts').selectAll('text').attr('class', '');
d3.select('#svg1 .nodes').selectAll('circle').attr('class', '');
d3.select('#svg1 .links').selectAll('line').attr('class', '');
}
//否则判断判断三个元素是否等于name值,等于则显示该值
else {
var name = $(this).val();
//搜索所有的节点
d3.select('#svg1 .nodes').selectAll('circle').attr('class', function(d) {
//输入节点id的小写等于name则显示,否则隐藏
if (d.id.toLowerCase().indexOf(name.toLowerCase()) >= 0) {
return '';
} else {
//优化:与该搜索节点相关联的节点均显示
//links链接的起始节点进行判断,如果其id等于name则显示这类节点
//注意: graph=data
for (var i = 0; i < graph.links.length; i++) {
//如果links的起点等于name,并且终点等于正在处理的则显示
if ((graph.links[i]['source'].id.toLowerCase().indexOf(name.toLowerCase()) >= 0) &&
(graph.links[i]['target'].id == d.id)) {
return '';
}
//如果links的终点等于name,并且起点等于正在处理的则显示
if ((graph.links[i]['target'].id.toLowerCase().indexOf(name.toLowerCase()) >= 0) &&
(graph.links[i]['source'].id == d.id)) {
return '';
}
}
return 'inactive'; //隐藏其他节点
}
});
//搜索texts
d3.select('#svg1 .texts').selectAll('text').attr('class', function(d) {
if (d.id.toLowerCase().indexOf(name.toLowerCase()) >= 0) {
return '';
} else {
//优化:与该搜索节点相关联的节点均显示
//links链接的起始节点进行判断,如果其id等于name则显示这类节点
//注意: graph=data
for (var i = 0; i < graph.links.length; i++) {
//如果links的起点等于name,并且终点等于正在处理的则显示
if ((graph.links[i]['source'].id.toLowerCase().indexOf(name.toLowerCase()) >= 0) &&
(graph.links[i]['target'].id == d.id)) {
return '';
}
//如果links的终点等于name,并且起点等于正在处理的则显示
if ((graph.links[i]['target'].id.toLowerCase().indexOf(name.toLowerCase()) >= 0) &&
(graph.links[i]['source'].id == d.id)) {
return '';
}
}
return 'inactive'; //隐藏其他节点
}
});
//搜索links 所有与搜索name相关联的边均显示
//显示相的邻边 注意 ||
//name=$(this).val():名字为键盘输入的内容
d3.select("#svg1 .links").selectAll('line').attr('class', function(d) {
if ((d.source.id.toLowerCase().indexOf(name.toLowerCase()) >= 0) ||
(d.target.id.toLowerCase().indexOf(name.toLowerCase()) >= 0)
) {
return '';
} else {
return 'inactive'; //隐藏
}
});
}
}); //end input
//加载Python获取的Json信息:六类实体详细属性信息
var info;
//d3.json获取数据
d3.json("all_data.json", function(error, data) {
if(error) throw error;
info = data;
});
});
script>
html>
源码下载地址:https://download.csdn.net/download/eastmount/10958879
希望这篇基础性文章对你有所帮助,尤其是新手。
哎,英语真心太差了,博士也不一定能考上,但我至少奋斗过,这个寒假每日每夜都忙碌着。同时也希望未来能有更多时间学习新知识,分享新知识。未来的路谁又知道,作为一名大学老师,希望自己能始终保持初心,教好自己的每一个学生;作为一名程序员,希望自己知道学无止境,写好自己的每一篇博文;作为一名主人公,希望自己能照顾好另一伴,爱她就像爱生命。我毫无阅历,毫无准备,我一头栽进我的命里,就像跌进一个深坑,从那一秒钟起,我的心里只有一个,就是你。
(By:Eastmount 2019-02-14 下午4点 http://blog.csdn.net/eastmount/)