Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

visjs-network#39: add penwidth support for dot language #24

Merged
merged 3 commits into from
Jul 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions examples/network/data/dotLanguage/dotEdgeStyles.html
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ <h1>DOT edge styles</h1>
<td align="center">arrowhead, arrowtail</td>
<td>Arrow style ("dot", "box", "crow", "curve", "icurve", "normal", "inv", "diamond", "tee", "vee")</td>
</tr>
<tr>
<td align='center'>width or penwidth</td>
<td>Edge width</td>
</tr>
</table>
</div>

Expand Down Expand Up @@ -153,6 +157,7 @@ <h1>DOT edge styles</h1>
'\n' +
' // Line styles\n' +
' lines -- solid[label="solid pink", color="pink"]; \n' +
' lines -- penwidth[label="penwidth=5", penwidth=5]; \n' +
' lines -- dashed[label="dashed green", style="dashed", color="green"]; \n' +
' lines -- dotted[label="dotted purple", style="dotted", color="purple"]; \n' +
'\n' +
Expand Down
275 changes: 270 additions & 5 deletions lib/network/dotparser.js
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,253 @@ function parseEdge(graph, from) {
}
}

/**
* As explained in [1], graphviz has limitations for combination of
* arrow[head|tail] and dir. If attribute list includes 'dir',
* following cases just be supported.
* 1. both or none + arrowhead, arrowtail
* 2. forward + arrowhead (arrowtail is not affedted)
* 3. back + arrowtail (arrowhead is not affected)
* [1] https://www.graphviz.org/doc/info/attrs.html#h:undir_note
*
* This function is called from parseAttributeList() to parse 'dir'
* attribute with given 'attr_names' and 'attr_list'.
* @param {Object} attr_names Array of attribute names
* @param {Object} attr_list Array of objects of attribute set
* @return {Object} attr_list Updated attr_list
*/
function parseDirAttribute(attr_names, attr_list) {
var i;
if (attr_names.includes('dir')) {
var idx = {}; // get index of 'arrows' and 'dir'
idx.arrows = {};
for (i = 0; i < attr_list.length; i++) {
if (attr_list[i].name === 'arrows') {
if (attr_list[i].value.to != null) {
idx.arrows.to = i;
} else if (attr_list[i].value.from != null) {
idx.arrows.from = i;
} else {
throw newSyntaxError('Invalid value of arrows');
}
} else if (attr_list[i].name === 'dir') {
idx.dir = i;
}
}

// first, add default arrow shape if it is not assigned to avoid error
var dir_type = attr_list[idx.dir].value;
if (!attr_names.includes('arrows')) {
if (dir_type === 'both') {
attr_list.push(
{'attr': attr_list[idx.dir].attr, 'name': 'arrows',
'value': {'to': {'enabled': true}}
}
);
idx.arrows.to = attr_list.length - 1;
attr_list.push(
{'attr': attr_list[idx.dir].attr, 'name': 'arrows',
'value': {'from': {'enabled': true}}
}
);
idx.arrows.from = attr_list.length - 1;
} else if (dir_type === 'forward') {
attr_list.push(
{'attr': attr_list[idx.dir].attr, 'name': 'arrows',
'value': {'to': {'enabled': true}}
}
);
idx.arrows.to = attr_list.length - 1;
} else if (dir_type === 'back') {
attr_list.push(
{'attr': attr_list[idx.dir].attr, 'name': 'arrows',
'value': {'from': {'enabled': true}}
}
);
idx.arrows.from = attr_list.length - 1;
} else if (dir_type === 'none') {
attr_list.push(
{'attr': attr_list[idx.dir].attr, 'name': 'arrows', 'value': ''}
);
idx.arrows.to = attr_list.length - 1;
} else {
throw newSyntaxError('Invalid dir type "' + dir_type + '"');
}
}

var from_type;
var to_type;
// update 'arrows' attribute from 'dir'.
if (dir_type === 'both') {
// both of shapes of 'from' and 'to' are given
if (idx.arrows.to && idx.arrows.from) {
to_type = attr_list[idx.arrows.to].value.to.type;
from_type = attr_list[idx.arrows.from].value.from.type;
attr_list[idx.arrows.to] = {
'attr': attr_list[idx.arrows.to].attr,
'name': attr_list[idx.arrows.to].name,
'value': {
'to': {'enabled': true, 'type': to_type},
'from': {'enabled': true, 'type': from_type}
}
};
attr_list.splice(idx.arrows.from, 1);

// shape of 'to' is assigned and use default to 'from'
} else if (idx.arrows.to) {
to_type = attr_list[idx.arrows.to].value.to.type;
from_type = 'arrow';
attr_list[idx.arrows.to] = {
'attr': attr_list[idx.arrows.to].attr,
'name': attr_list[idx.arrows.to].name,
'value': {
'to': {'enabled': true, 'type': to_type},
'from': {'enabled': true, 'type': from_type}
}
};

// only shape of 'from' is assigned and use default for 'to'
} else if (idx.arrows.from) {
to_type = 'arrow';
from_type = attr_list[idx.arrows.from].value.from.type;
attr_list[idx.arrows.from] = {
'attr': attr_list[idx.arrows.from].attr,
'name': attr_list[idx.arrows.from].name,
'value': {
'to': {'enabled': true, 'type': to_type},
'from': {'enabled': true, 'type': from_type}
}
};
}

} else if (dir_type === 'back') {
// given both of shapes, but use only 'from'
if (idx.arrows.to && idx.arrows.from) {
to_type = '';
from_type = attr_list[idx.arrows.from].value.from.type;
attr_list[idx.arrows.from] = {
'attr': attr_list[idx.arrows.from].attr,
'name': attr_list[idx.arrows.from].name,
'value': {
'to': {'enabled': true, 'type': to_type},
'from': {'enabled': true, 'type': from_type}
}
};

// given shape of 'to', but does not use it
} else if (idx.arrows.to) {
to_type = '';
from_type = 'arrow';
idx.arrows.from = idx.arrows.to;
attr_list[idx.arrows.from] = {
'attr': attr_list[idx.arrows.from].attr,
'name': attr_list[idx.arrows.from].name,
'value': {
'to': {'enabled': true, 'type': to_type},
'from': {'enabled': true, 'type': from_type}
}
};

// assign given 'from' shape
} else if (idx.arrows.from) {
to_type = '';
from_type = attr_list[idx.arrows.from].value.from.type;
attr_list[idx.arrows.to] = {
'attr': attr_list[idx.arrows.from].attr,
'name': attr_list[idx.arrows.from].name,
'value': {
'to': {'enabled': true, 'type': to_type},
'from': {'enabled': true, 'type': from_type}
}
};
}

attr_list[idx.arrows.from] = {
'attr': attr_list[idx.arrows.from].attr,
'name': attr_list[idx.arrows.from].name,
'value': {
'from': {
'enabled': true,
'type': attr_list[idx.arrows.from].value.from.type}
}
};

} else if (dir_type === 'none') {
var idx_arrow;
if (idx.arrows.to) {
idx_arrow = idx.arrows.to;
} else {
idx_arrow = idx.arrows.from;
}

attr_list[idx_arrow] = {
'attr': attr_list[idx_arrow].attr,
'name': attr_list[idx_arrow].name,
'value': ''
};

} else if (dir_type === 'forward'){
// given both of shapes, but use only 'to'
if (idx.arrows.to && idx.arrows.from) {
to_type = attr_list[idx.arrows.to].value.to.type;
from_type = '';
attr_list[idx.arrows.to] = {
'attr': attr_list[idx.arrows.to].attr,
'name': attr_list[idx.arrows.to].name,
'value': {
'to': {'enabled': true, 'type': to_type},
'from': {'enabled': true, 'type': from_type}
}
};

// assign given 'to' shape
} else if (idx.arrows.to) {
to_type = attr_list[idx.arrows.to].value.to.type;
from_type = '';
attr_list[idx.arrows.to] = {
'attr': attr_list[idx.arrows.to].attr,
'name': attr_list[idx.arrows.to].name,
'value': {
'to': {'enabled': true, 'type': to_type},
'from': {'enabled': true, 'type': from_type}
}
};

// given shape of 'from', but does not use it
} else if (idx.arrows.from) {
to_type = 'arrow';
from_type = '';
idx.arrows.to = idx.arrows.from;
attr_list[idx.arrows.to] = {
'attr': attr_list[idx.arrows.to].attr,
'name': attr_list[idx.arrows.to].name,
'value': {
'to': {'enabled': true, 'type': to_type},
'from': {'enabled': true, 'type': from_type}
}
};
}

attr_list[idx.arrows.to] = {
'attr': attr_list[idx.arrows.to].attr,
'name': attr_list[idx.arrows.to].name,
'value': {
'to': {
'enabled': true,
'type': attr_list[idx.arrows.to].value.to.type
}
}
};
} else {
throw newSyntaxError('Invalid dir type "' + dir_type + '"');
}

// remove 'dir' attribute no need anymore
attr_list.splice(idx.dir, 1);
}
return attr_list;
}

/**
* Parse a set with attributes,
* for example [label="1.000", shape=solid]
Expand Down Expand Up @@ -719,7 +966,7 @@ function parseAttributeList() {
};

/**
* 'attr_list' containes attributes for checking some of them are affected
* 'attr_list' contains attributes for checking if some of them are affected
* later. For instance, both of 'arrowhead' and 'dir' (edge style defined
* in DOT) make changes to 'arrows' attribute in vis.
*/
Expand Down Expand Up @@ -756,13 +1003,13 @@ function parseAttributeList() {
if (name === 'arrowhead') {
arrowType = arrowTypes[value];
name = 'arrows';
value = {to: {enabled:true, type: arrowType}};
value = {'to': {'enabled': true, 'type': arrowType}};
}

if (name === 'arrowtail') {
arrowType = arrowTypes[value];
name = 'arrows';
value = {from: {enabled:true, type: arrowType}};
value = {'from': {'enabled': true, 'type': arrowType}};
}

attr_list.push(
Expand All @@ -771,7 +1018,7 @@ function parseAttributeList() {
attr_names.push(name);

getToken();
if (token ==',') {
if (token == ',') {
getToken();
}
}
Expand Down Expand Up @@ -1012,7 +1259,25 @@ function parseAttributeList() {
attr_list.splice(idx.dir, 1);
}

var nof_attr_list = attr_list.length;
// parse 'penwidth'
var nof_attr_list;
if (attr_names.includes('penwidth')) {
var tmp_attr_list = [];

nof_attr_list = attr_list.length;
for (i = 0; i < nof_attr_list; i++) {
// exclude 'width' from attr_list if 'penwidth' exists
if (attr_list[i].name !== 'width') {
if (attr_list[i].name === 'penwidth') {
attr_list[i].name = 'width';
}
tmp_attr_list.push(attr_list[i]);
}
}
attr_list = tmp_attr_list;
}

nof_attr_list = attr_list.length;
for (i = 0; i < nof_attr_list; i++) {
setValue(attr_list[i].attr, attr_list[i].name, attr_list[i].value);
}
Expand Down