mirror of
https://github.com/UnickSoft/graphonline.git
synced 2025-07-04 00:36:45 +00:00
Add multigraph support. Visualization and some algorithms.
This commit is contained in:
parent
c580c5aef9
commit
063c2f9ec0
@ -240,6 +240,20 @@
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#CanvasMessage
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
right: 48px;
|
||||||
|
z-index: 10;
|
||||||
|
-webkit-touch-callout: none; /* iOS Safari */
|
||||||
|
-webkit-user-select: none; /* Safari */
|
||||||
|
-khtml-user-select: none; /* Konqueror HTML */
|
||||||
|
-moz-user-select: none; /* Firefox */
|
||||||
|
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||||
|
user-select: none; /* Non-prefixed version, currently
|
||||||
|
supported by Chrome and Opera */
|
||||||
|
}
|
||||||
|
|
||||||
.MarginLeft
|
.MarginLeft
|
||||||
{
|
{
|
||||||
margin-right: 32px !important;
|
margin-right: 32px !important;
|
||||||
@ -326,3 +340,18 @@
|
|||||||
min-width: 0px;
|
min-width: 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.InlineStyle
|
||||||
|
{
|
||||||
|
display:inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PaddingRight
|
||||||
|
{
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#NewEdgeAction
|
||||||
|
{
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
@ -165,4 +165,12 @@
|
|||||||
$g_lang["alpha"] = "Opacity";
|
$g_lang["alpha"] = "Opacity";
|
||||||
|
|
||||||
$g_lang["background_style"] = "Background color";
|
$g_lang["background_style"] = "Background color";
|
||||||
|
|
||||||
|
$g_lang["adjacency_matrix_multigraph_description"] = "Multigraph matrix contains weight of minimum edges between vertices.";
|
||||||
|
|
||||||
|
$g_lang["graph_is_multi_message"] = "Multigraph does not support all algorithms";
|
||||||
|
$g_lang["graph_is_general_message"] = "";
|
||||||
|
|
||||||
|
$g_lang["replace_edge"] = "replace current";
|
||||||
|
$g_lang["add_edge"] = "add (multigraph)";
|
||||||
?>
|
?>
|
||||||
|
@ -168,4 +168,12 @@
|
|||||||
$g_lang["alpha"] = "Прозрачность";
|
$g_lang["alpha"] = "Прозрачность";
|
||||||
|
|
||||||
$g_lang["background_style"] = "Цвет фона";
|
$g_lang["background_style"] = "Цвет фона";
|
||||||
|
|
||||||
|
$g_lang["adjacency_matrix_multigraph_description"] = "Для мультиграфа матрица содержит значения минимальных дуг между вершинами.";
|
||||||
|
|
||||||
|
$g_lang["replace_edge"] = "заменить текущую";
|
||||||
|
$g_lang["add_edge"] = "добавить (мультиграф)";
|
||||||
|
|
||||||
|
$g_lang["graph_is_multi_message"] = "Мультиграф не поддерживает все алгоритмы";
|
||||||
|
$g_lang["graph_is_general_message"] = "";
|
||||||
?>
|
?>
|
||||||
|
@ -135,7 +135,11 @@ BaseAlgorithm.prototype.getPriority = function()
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Algorithm support multi graph
|
||||||
|
BaseAlgorithm.prototype.IsSupportMultiGraph = function()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default handler.
|
* Default handler.
|
||||||
|
@ -79,6 +79,8 @@ Application.prototype.redrawGraph = function()
|
|||||||
if (!this.isTimerRender)
|
if (!this.isTimerRender)
|
||||||
{
|
{
|
||||||
this._redrawGraph();
|
this._redrawGraph();
|
||||||
|
|
||||||
|
this.GraphTypeChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,9 +419,9 @@ Application.prototype.AddNewVertex = function(vertex)
|
|||||||
return this.graph.AddNewVertex(vertex);
|
return this.graph.AddNewVertex(vertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
Application.prototype.AddNewEdge = function(edge)
|
Application.prototype.AddNewEdge = function(edge, replaceIfExists)
|
||||||
{
|
{
|
||||||
return this.graph.AddNewEdge(edge);
|
return this.graph.AddNewEdge(edge, replaceIfExists);
|
||||||
}
|
}
|
||||||
|
|
||||||
Application.prototype.CreateNewGraph = function(x, y)
|
Application.prototype.CreateNewGraph = function(x, y)
|
||||||
@ -439,20 +441,35 @@ Application.prototype.CreateNewGraphEx = function(x, y, vertexEnume)
|
|||||||
return this.graph.AddNewVertex(new BaseVertex(x, y, vertexEnume));
|
return this.graph.AddNewVertex(new BaseVertex(x, y, vertexEnume));
|
||||||
}
|
}
|
||||||
|
|
||||||
Application.prototype.CreateNewArc = function(graph1, graph2, isDirect, weight)
|
Application.prototype.CreateNewArc = function(graph1, graph2, isDirect, weight, replaceIfExist)
|
||||||
{
|
{
|
||||||
var edge = this.AddNewEdge(new BaseEdge(graph1, graph2, isDirect, weight));
|
var edge = this.AddNewEdge(new BaseEdge(graph1, graph2, isDirect, weight), replaceIfExist);
|
||||||
|
|
||||||
// Make cruvled for pair.
|
|
||||||
var edgeObject = this.graph.edges[edge];
|
var edgeObject = this.graph.edges[edge];
|
||||||
if (edgeObject.hasPair)
|
var hasPair = this.graph.hasPair(edgeObject);
|
||||||
|
var neighbourEdges = this.graph.getNeighbourEdges(edgeObject);
|
||||||
|
|
||||||
|
if (hasPair)
|
||||||
{
|
{
|
||||||
if (edgeObject.model.default)
|
if (edgeObject.model.default)
|
||||||
edgeObject.model.type = EdgeModels.cruvled;
|
edgeObject.model.type = EdgeModels.cruvled;
|
||||||
|
|
||||||
var pairEdge = this.FindEdge(graph2.id, graph1.id);
|
var pairEdge = this.graph.FindPairFor(edgeObject);
|
||||||
if (pairEdge.model.default)
|
if (pairEdge.model.default)
|
||||||
|
{
|
||||||
pairEdge.model.type = EdgeModels.cruvled;
|
pairEdge.model.type = EdgeModels.cruvled;
|
||||||
|
if (pairEdge.vertex1 == edgeObject.vertex1 && pairEdge.vertex2 == edgeObject.vertex2)
|
||||||
|
pairEdge.model.curvedValue = -pairEdge.model.curvedValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (neighbourEdges.length >= 2)
|
||||||
|
{
|
||||||
|
var cruvled = this.GetAvalibleCruvledValue(neighbourEdges, edgeObject);
|
||||||
|
if (edgeObject.model.default)
|
||||||
|
{
|
||||||
|
edgeObject.model.type = EdgeModels.cruvled;
|
||||||
|
edgeObject.model.curvedValue = cruvled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return edge;
|
return edge;
|
||||||
@ -462,16 +479,18 @@ Application.prototype.DeleteEdge = function(edgeObject)
|
|||||||
{
|
{
|
||||||
var vertex1 = edgeObject.vertex1;
|
var vertex1 = edgeObject.vertex1;
|
||||||
var vertex2 = edgeObject.vertex2;
|
var vertex2 = edgeObject.vertex2;
|
||||||
var hasPair = edgeObject.hasPair;
|
|
||||||
|
var hasPair = this.graph.hasPair(edgeObject);
|
||||||
|
|
||||||
this.graph.DeleteEdge(edgeObject);
|
this.graph.DeleteEdge(edgeObject);
|
||||||
|
|
||||||
// Make line for pair.
|
// Make line for pair.
|
||||||
if (hasPair)
|
if (hasPair)
|
||||||
{
|
{
|
||||||
var edgeObject = this.FindEdge(vertex2.id, vertex1.id);
|
var pairEdges = this.FindAllEdges(vertex2.id, vertex1.id);
|
||||||
if (edgeObject.model.default)
|
|
||||||
edgeObject.model.type = EdgeModels.line;
|
if (pairEdges.length == 1 && pairEdges[0].model.default)
|
||||||
|
pairEdges[0].model.type = EdgeModels.line;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -508,6 +527,16 @@ Application.prototype.FindEdge = function(id1, id2)
|
|||||||
return this.graph.FindEdge(id1, id2);
|
return this.graph.FindEdge(id1, id2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Application.prototype.FindEdgeAny = function(id1, id2)
|
||||||
|
{
|
||||||
|
return this.graph.FindEdgeAny(id1, id2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Application.prototype.FindAllEdges = function(id1, id2)
|
||||||
|
{
|
||||||
|
return this.graph.FindAllEdges(id1, id2);
|
||||||
|
}
|
||||||
|
|
||||||
Application.prototype.FindPath = function(graph1, graph2)
|
Application.prototype.FindPath = function(graph1, graph2)
|
||||||
{
|
{
|
||||||
var creator = new GraphMLCreater(this.graph.vertices, this.graph.edges);
|
var creator = new GraphMLCreater(this.graph.vertices, this.graph.edges);
|
||||||
@ -1501,3 +1530,39 @@ Application.prototype.ResetBackgroundStyle = function ()
|
|||||||
this.isBackgroundCommonStyleCustom = false;
|
this.isBackgroundCommonStyleCustom = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Application.prototype.GetAvalibleCruvledValue = function(neighbourEdges, originalEdge)
|
||||||
|
{
|
||||||
|
var values = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < neighbourEdges.length; i ++)
|
||||||
|
{
|
||||||
|
var edge = neighbourEdges[i];
|
||||||
|
var sameDirection = (originalEdge.vertex1.id == edge.vertex1.id);
|
||||||
|
if (edge.model.type == EdgeModels.cruvled)
|
||||||
|
{
|
||||||
|
values[(sameDirection ? edge.model.curvedValue : -edge.model.curvedValue)] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var changeValue = DefaultHandler.prototype.curvedValue;
|
||||||
|
var defaultValue = 0.0;
|
||||||
|
var maxSearch = 10;
|
||||||
|
|
||||||
|
for (var i = 1; i < maxSearch; i ++)
|
||||||
|
{
|
||||||
|
value = i * changeValue;
|
||||||
|
if (!values.hasOwnProperty(value))
|
||||||
|
return value;
|
||||||
|
|
||||||
|
value = - i * changeValue;
|
||||||
|
if (!values.hasOwnProperty(value))
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Application.prototype.GraphTypeChanged = function()
|
||||||
|
{
|
||||||
|
$("#CanvasMessage").text(this.graph.isMulti() ? g_GrapsIsMultiMessage : g_GrapsIsGeneralMessage);
|
||||||
|
}
|
@ -14,8 +14,6 @@ function BaseEdge(vertex1, vertex2, isDirect, weight)
|
|||||||
this.isDirect = isDirect;
|
this.isDirect = isDirect;
|
||||||
this.weight = 0;
|
this.weight = 0;
|
||||||
this.text = "";
|
this.text = "";
|
||||||
// For direct graph, has pair edge or not.
|
|
||||||
this.hasPair = false;
|
|
||||||
this.useWeight = false;
|
this.useWeight = false;
|
||||||
this.id = 0;
|
this.id = 0;
|
||||||
this.model = new EdgeModel();
|
this.model = new EdgeModel();
|
||||||
@ -32,7 +30,6 @@ BaseEdge.prototype.SaveToXML = function ()
|
|||||||
"isDirect=\"" + this.isDirect + "\" " +
|
"isDirect=\"" + this.isDirect + "\" " +
|
||||||
"weight=\"" + this.weight + "\" " +
|
"weight=\"" + this.weight + "\" " +
|
||||||
"useWeight=\"" + this.useWeight + "\" " +
|
"useWeight=\"" + this.useWeight + "\" " +
|
||||||
"hasPair=\"" + this.hasPair + "\" " +
|
|
||||||
"id=\"" + this.id + "\" " +
|
"id=\"" + this.id + "\" " +
|
||||||
"text=\"" + this.text + "\" " +
|
"text=\"" + this.text + "\" " +
|
||||||
"arrayStyleStart=\"" + this.arrayStyleStart + "\" " +
|
"arrayStyleStart=\"" + this.arrayStyleStart + "\" " +
|
||||||
@ -99,33 +96,7 @@ BaseEdge.prototype.HitTest = function (pos)
|
|||||||
|
|
||||||
BaseEdge.prototype.GetEdgePositionsShift = function()
|
BaseEdge.prototype.GetEdgePositionsShift = function()
|
||||||
{
|
{
|
||||||
var pairShift = this.vertex1.model.diameter * 0.25;
|
return this.GetEdgePositions();
|
||||||
var shift = (this.hasPair ? pairShift : 0);
|
|
||||||
|
|
||||||
if (shift == 0 || this.model.type == EdgeModels.cruvled)
|
|
||||||
{
|
|
||||||
return this.GetEdgePositions();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var position1 = this.vertex1.position;
|
|
||||||
var position2 = this.vertex2.position;
|
|
||||||
var diameter1 = this.vertex1.model.diameter;
|
|
||||||
var diameter2 = this.vertex2.model.diameter;
|
|
||||||
|
|
||||||
var direction = position1.subtract(position2);
|
|
||||||
direction.normalize(1.0);
|
|
||||||
var normal = direction.normal();
|
|
||||||
direction = direction.multiply(0.5);
|
|
||||||
position1 = position1.subtract(normal.multiply(shift));
|
|
||||||
position2 = position2.subtract(normal.multiply(shift));
|
|
||||||
diameter1 = Math.sqrt(diameter1 * diameter1 - shift * shift);
|
|
||||||
diameter2 = Math.sqrt(diameter2 * diameter2 - shift * shift);
|
|
||||||
var res = [];
|
|
||||||
res.push(position1.subtract(direction.multiply(diameter1)));
|
|
||||||
res.push(position2.subtract(direction.multiply(-diameter2)));
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseEdge.prototype.GetEdgePositions = function()
|
BaseEdge.prototype.GetEdgePositions = function()
|
||||||
|
@ -163,7 +163,7 @@ BaseEdgeDrawer.prototype.Draw = function(baseEdge, arcStyle)
|
|||||||
|
|
||||||
if (baseEdge.GetText().length > 0)
|
if (baseEdge.GetText().length > 0)
|
||||||
{
|
{
|
||||||
this.DrawWeight(positions[0], positions[1], baseEdge.GetText(), arcStyle, baseEdge.hasPair);
|
this.DrawWeight(positions[0], positions[1], baseEdge.GetText(), arcStyle, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,7 +274,7 @@ BaseEdgeDrawer.prototype.GetTextCenterPoint = function (position1, position2, ha
|
|||||||
}
|
}
|
||||||
|
|
||||||
var textShift = Math.min(12 / position1.subtract(position2).length(), 0.4);
|
var textShift = Math.min(12 / position1.subtract(position2).length(), 0.4);
|
||||||
var centerPoint = Point.interpolate(position1, position2, 0.5 + (hasPair ? textShift : 0));
|
var centerPoint = Point.interpolate(position1, position2, 0.5);
|
||||||
if (position1.equals(position2))
|
if (position1.equals(position2))
|
||||||
{
|
{
|
||||||
centerPoint.y = centerPoint.y - Math.cos(arcStyle.loopShiftAngel) * arcStyle.sizeOfLoop * 2;
|
centerPoint.y = centerPoint.y - Math.cos(arcStyle.loopShiftAngel) * arcStyle.sizeOfLoop * 2;
|
||||||
|
@ -419,7 +419,7 @@ ConnectionGraphHandler.prototype.firstObject = null;
|
|||||||
|
|
||||||
ConnectionGraphHandler.prototype.AddNewEdge = function(selectedObject, isDirect)
|
ConnectionGraphHandler.prototype.AddNewEdge = function(selectedObject, isDirect)
|
||||||
{
|
{
|
||||||
this.app.CreateNewArc(this.firstObject, selectedObject, isDirect, document.getElementById('EdgeWeight').value);
|
this.app.CreateNewArc(this.firstObject, selectedObject, isDirect, document.getElementById('EdgeWeight').value, $("#RadiosReplaceEdge").prop("checked"));
|
||||||
this.SelectFirst();
|
this.SelectFirst();
|
||||||
this.app.NeedRedraw();
|
this.app.NeedRedraw();
|
||||||
}
|
}
|
||||||
@ -431,6 +431,26 @@ ConnectionGraphHandler.prototype.SelectVertex = function(selectedObject)
|
|||||||
var direct = false;
|
var direct = false;
|
||||||
var handler = this;
|
var handler = this;
|
||||||
var dialogButtons = {};
|
var dialogButtons = {};
|
||||||
|
|
||||||
|
if (!this.app.graph.isMulti())
|
||||||
|
{
|
||||||
|
var hasEdge = this.app.graph.FindEdgeAny(this.firstObject.id, selectedObject.id);
|
||||||
|
var hasReverseEdge = this.app.graph.FindEdgeAny(selectedObject.id, this.firstObject.id);
|
||||||
|
|
||||||
|
if (hasEdge == null && hasReverseEdge == null)
|
||||||
|
{
|
||||||
|
$("#RadiosReplaceEdge").prop("checked", true);
|
||||||
|
$("#NewEdgeAction" ).hide();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$( "#NewEdgeAction" ).show();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$("#RadiosAddEdge").prop("checked", true);
|
||||||
|
$("#NewEdgeAction" ).hide();
|
||||||
|
}
|
||||||
|
|
||||||
dialogButtons[g_orintEdge] = function() {
|
dialogButtons[g_orintEdge] = function() {
|
||||||
handler.AddNewEdge(selectedObject, true);
|
handler.AddNewEdge(selectedObject, true);
|
||||||
$( this ).dialog( "close" );
|
$( this ).dialog( "close" );
|
||||||
@ -636,6 +656,11 @@ ShowAdjacencyMatrix.prototype.show = function()
|
|||||||
$( "#AdjacencyMatrixField" ).val(this.app.GetAdjacencyMatrix());
|
$( "#AdjacencyMatrixField" ).val(this.app.GetAdjacencyMatrix());
|
||||||
$( "#BadMatrixFormatMessage" ).hide();
|
$( "#BadMatrixFormatMessage" ).hide();
|
||||||
|
|
||||||
|
if (this.app.graph.isMulti())
|
||||||
|
$( "#AdjacencyMatrixMultiGraphDesc").show();
|
||||||
|
else
|
||||||
|
$( "#AdjacencyMatrixMultiGraphDesc").hide();
|
||||||
|
|
||||||
$( "#adjacencyMatrix" ).dialog({
|
$( "#adjacencyMatrix" ).dialog({
|
||||||
resizable: false,
|
resizable: false,
|
||||||
height: "auto",
|
height: "auto",
|
||||||
|
161
script/Graph.js
161
script/Graph.js
@ -16,6 +16,8 @@ function Graph()
|
|||||||
this.uidEdge = 10000;
|
this.uidEdge = 10000;
|
||||||
// Has direction edge.
|
// Has direction edge.
|
||||||
this.hasDirect = false;
|
this.hasDirect = false;
|
||||||
|
// Is graph multi
|
||||||
|
this.isMultiGraph = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// infinity
|
// infinity
|
||||||
@ -37,44 +39,34 @@ Graph.prototype.AddNewEdgeSafe = function(graph1, graph2, isDirect, weight)
|
|||||||
return this.AddNewEdge(new BaseEdge(graph1, graph2, isDirect, weight));
|
return this.AddNewEdge(new BaseEdge(graph1, graph2, isDirect, weight));
|
||||||
}
|
}
|
||||||
|
|
||||||
Graph.prototype.AddNewEdge = function(edge)
|
Graph.prototype.AddNewEdge = function(edge, replaceIfExists)
|
||||||
{
|
{
|
||||||
edge.id = this.uidEdge;
|
edge.id = this.uidEdge;
|
||||||
this.uidEdge = this.uidEdge + 1;
|
this.uidEdge = this.uidEdge + 1;
|
||||||
|
|
||||||
var edge1 = this.FindEdge(edge.vertex1.id, edge.vertex2.id);
|
var edge1 = this.FindEdgeAny(edge.vertex1.id, edge.vertex2.id);
|
||||||
var edgeRevert = this.FindEdge(edge.vertex2.id, edge.vertex1.id);
|
var edgeRevert = this.FindEdgeAny(edge.vertex2.id, edge.vertex1.id);
|
||||||
if (!edge.isDirect)
|
if (!edge.isDirect)
|
||||||
{
|
{
|
||||||
if (edge1 != null)
|
if (edge1 != null && replaceIfExists)
|
||||||
{
|
|
||||||
this.DeleteEdge(edge1);
|
this.DeleteEdge(edge1);
|
||||||
}
|
if (edgeRevert != null && replaceIfExists)
|
||||||
if (edgeRevert != null)
|
|
||||||
{
|
|
||||||
this.DeleteEdge(edgeRevert);
|
this.DeleteEdge(edgeRevert);
|
||||||
}
|
|
||||||
this.edges.push(edge);
|
this.edges.push(edge);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (edge1 != null)
|
if (edge1 != null && replaceIfExists)
|
||||||
{
|
|
||||||
this.DeleteEdge(edge1);
|
this.DeleteEdge(edge1);
|
||||||
}
|
if (edgeRevert != null && !edgeRevert.isDirect && replaceIfExists)
|
||||||
if (edgeRevert != null && !edgeRevert.isDirect)
|
|
||||||
{
|
|
||||||
this.DeleteEdge(edgeRevert);
|
this.DeleteEdge(edgeRevert);
|
||||||
}
|
|
||||||
else if (edgeRevert != null)
|
|
||||||
{
|
|
||||||
edgeRevert.hasPair = true;
|
|
||||||
edge.hasPair = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.edges.push(edge);
|
this.edges.push(edge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isMultiGraph = this.checkMutiGraph();
|
||||||
|
|
||||||
return this.edges.length - 1;
|
return this.edges.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,15 +76,11 @@ Graph.prototype.DeleteEdge = function(edgeObject)
|
|||||||
var index = this.edges.indexOf(edgeObject);
|
var index = this.edges.indexOf(edgeObject);
|
||||||
if (index > -1)
|
if (index > -1)
|
||||||
{
|
{
|
||||||
var edgeRevert = this.FindEdge(edgeObject.vertex2.id, edgeObject.vertex1.id);
|
|
||||||
if (edgeRevert != null && edgeRevert.isDirect)
|
|
||||||
{
|
|
||||||
edgeRevert.hasPair = false;
|
|
||||||
}
|
|
||||||
this.edges.splice(index, 1);
|
this.edges.splice(index, 1);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
this.isMultiGraph = this.checkMutiGraph();
|
||||||
|
}
|
||||||
|
|
||||||
Graph.prototype.DeleteVertex = function(vertexObject)
|
Graph.prototype.DeleteVertex = function(vertexObject)
|
||||||
{
|
{
|
||||||
@ -126,7 +114,13 @@ Graph.prototype.FindVertex = function(id)
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// depricated
|
||||||
Graph.prototype.FindEdge = function(id1, id2)
|
Graph.prototype.FindEdge = function(id1, id2)
|
||||||
|
{
|
||||||
|
return this.FindEdgeAny(id1, id2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.FindEdgeAny = function(id1, id2)
|
||||||
{
|
{
|
||||||
var res = null;
|
var res = null;
|
||||||
for (var i = 0; i < this.edges.length; i++)
|
for (var i = 0; i < this.edges.length; i++)
|
||||||
@ -142,6 +136,43 @@ Graph.prototype.FindEdge = function(id1, id2)
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Graph.prototype.FindEdgeMin = function(id1, id2)
|
||||||
|
{
|
||||||
|
var res = null;
|
||||||
|
var minWeight = this.infinity;
|
||||||
|
for (var i = 0; i < this.edges.length; i++)
|
||||||
|
{
|
||||||
|
var edge = this.edges[i];
|
||||||
|
if ((edge.vertex1.id == id1 && edge.vertex2.id == id2)
|
||||||
|
|| (!edge.isDirect && edge.vertex1.id == id2 && edge.vertex2.id == id1))
|
||||||
|
{
|
||||||
|
if (edge.weight < minWeight)
|
||||||
|
{
|
||||||
|
res = edge;
|
||||||
|
minWeight = edge.weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.FindAllEdges = function(id1, id2)
|
||||||
|
{
|
||||||
|
var res = [];
|
||||||
|
for (var i = 0; i < this.edges.length; i++)
|
||||||
|
{
|
||||||
|
var edge = this.edges[i];
|
||||||
|
if ((edge.vertex1.id == id1 && edge.vertex2.id == id2)
|
||||||
|
|| (!edge.isDirect && edge.vertex1.id == id2 && edge.vertex2.id == id1))
|
||||||
|
{
|
||||||
|
res.push(edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
Graph.prototype.GetAdjacencyMatrixStr = function ()
|
Graph.prototype.GetAdjacencyMatrixStr = function ()
|
||||||
{
|
{
|
||||||
var matrix = "";
|
var matrix = "";
|
||||||
@ -149,7 +180,7 @@ Graph.prototype.GetAdjacencyMatrixStr = function ()
|
|||||||
{
|
{
|
||||||
for (var j = 0; j < this.vertices.length; j++)
|
for (var j = 0; j < this.vertices.length; j++)
|
||||||
{
|
{
|
||||||
var edge = this.FindEdge (this.vertices[i].id, this.vertices[j].id);
|
var edge = this.FindEdgeMin (this.vertices[i].id, this.vertices[j].id);
|
||||||
if (edge != null)
|
if (edge != null)
|
||||||
{
|
{
|
||||||
matrix += edge.weight;
|
matrix += edge.weight;
|
||||||
@ -182,7 +213,7 @@ Graph.prototype.GetAdjacencyMatrix = function ()
|
|||||||
for (var j = 0; j < this.vertices.length; j ++)
|
for (var j = 0; j < this.vertices.length; j ++)
|
||||||
{
|
{
|
||||||
var v2 = this.vertices[j];
|
var v2 = this.vertices[j];
|
||||||
var edge = this.FindEdge(v1.id, v2.id);
|
var edge = this.FindEdgeMin(v1.id, v2.id);
|
||||||
if (edge != null)
|
if (edge != null)
|
||||||
{
|
{
|
||||||
matrix[i][j] = edge.GetWeight();
|
matrix[i][j] = edge.GetWeight();
|
||||||
@ -845,6 +876,8 @@ Graph.prototype.LoadFromXML = function (xmlText, additionalData)
|
|||||||
{
|
{
|
||||||
additionalData["data"] = $additional.attr('data');
|
additionalData["data"] = $additional.attr('data');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.isMultiGraph = this.checkMutiGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
Graph.prototype.hasDirectEdge = function ()
|
Graph.prototype.hasDirectEdge = function ()
|
||||||
@ -910,3 +943,75 @@ Graph.prototype.getGraphBBox = function (viewportSize)
|
|||||||
|
|
||||||
return new Rect(pointMin, pointMax);
|
return new Rect(pointMin, pointMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Graph.prototype.hasPair = function (edge)
|
||||||
|
{
|
||||||
|
return this.FindPairFor(edge) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.FindPairFor = function (edge)
|
||||||
|
{
|
||||||
|
var res = this.getNeighbourEdges(edge);
|
||||||
|
|
||||||
|
return res.length == 1 ? res[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.getNeighbourEdges = function (edge)
|
||||||
|
{
|
||||||
|
var res = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < this.edges.length; i++)
|
||||||
|
{
|
||||||
|
var curEdge = this.edges[i];
|
||||||
|
if (curEdge == edge)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((curEdge.vertex1.id == edge.vertex1.id &&
|
||||||
|
curEdge.vertex2.id == edge.vertex2.id) ||
|
||||||
|
(curEdge.vertex1.id == edge.vertex2.id &&
|
||||||
|
curEdge.vertex2.id == edge.vertex1.id))
|
||||||
|
{
|
||||||
|
res.push(curEdge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.checkMutiGraph = function ()
|
||||||
|
{
|
||||||
|
var res = false;
|
||||||
|
|
||||||
|
var start = {};
|
||||||
|
|
||||||
|
for (var i = 0; i < this.edges.length; i++)
|
||||||
|
{
|
||||||
|
var edge = this.edges[i];
|
||||||
|
if (start.hasOwnProperty(edge.vertex1.id) &&
|
||||||
|
start[edge.vertex1.id] == edge.vertex2.id)
|
||||||
|
{
|
||||||
|
res = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
start[edge.vertex1.id] = edge.vertex2.id;
|
||||||
|
if (!edge.isDirect)
|
||||||
|
{
|
||||||
|
if (start.hasOwnProperty(edge.vertex2.id) &&
|
||||||
|
start[edge.vertex2.id] == edge.vertex1.id)
|
||||||
|
{
|
||||||
|
res = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
start[edge.vertex2.id] = edge.vertex1.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph.prototype.isMulti = function ()
|
||||||
|
{
|
||||||
|
return this.isMultiGraph;
|
||||||
|
}
|
||||||
|
@ -126,6 +126,21 @@ function createAlgorithmMenu()
|
|||||||
application.SetHandlerMode(this.id);
|
application.SetHandlerMode(this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var eventData = {};
|
||||||
|
eventData.index = i;
|
||||||
|
eventData.object = clone;
|
||||||
|
eventData.algorithm = algorithm;
|
||||||
|
|
||||||
|
$("#openAlgorithmList").bind('click', eventData, function (_eventData) {
|
||||||
|
var data = _eventData.data;
|
||||||
|
var algorithm = g_Algorithms[g_AlgorithmIds.indexOf(data.algorithm.id)](application.graph, application);
|
||||||
|
|
||||||
|
if (application.graph.isMulti() && !algorithm.IsSupportMultiGraph())
|
||||||
|
$(data.object).hide();
|
||||||
|
else
|
||||||
|
$(data.object).show();
|
||||||
|
});
|
||||||
|
|
||||||
list.appendChild(clone);
|
list.appendChild(clone);
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ BFSAlgorithm.prototype.bfs = function(vertex, vertexArray, edgeArray)
|
|||||||
for (var i = 0; i < this.graph.vertices.length; i ++)
|
for (var i = 0; i < this.graph.vertices.length; i ++)
|
||||||
{
|
{
|
||||||
var nextVertex = this.graph.vertices[i];
|
var nextVertex = this.graph.vertices[i];
|
||||||
var edge = this.graph.FindEdge(vertex.id, nextVertex.id);
|
var edge = this.graph.FindEdgeAny(vertex.id, nextVertex.id);
|
||||||
if (edge && !vertexArray.includes(nextVertex))
|
if (edge && !vertexArray.includes(nextVertex))
|
||||||
{
|
{
|
||||||
edgeArray.push(edge);
|
edgeArray.push(edge);
|
||||||
@ -104,6 +104,12 @@ BFSAlgorithm.prototype.bfs = function(vertex, vertexArray, edgeArray)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Algorithm support multi graph
|
||||||
|
BFSAlgorithm.prototype.IsSupportMultiGraph = function()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Factory for connected components.
|
// Factory for connected components.
|
||||||
function CreateBFSAlgorithm(graph, app)
|
function CreateBFSAlgorithm(graph, app)
|
||||||
{
|
{
|
||||||
|
@ -369,6 +369,12 @@ Coloring.prototype.getPriority = function()
|
|||||||
return -9.0;
|
return -9.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Algorithm support multi graph
|
||||||
|
Coloring.prototype.IsSupportMultiGraph = function()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Factory for connected components.
|
// Factory for connected components.
|
||||||
function CreateColoring(graph, app)
|
function CreateColoring(graph, app)
|
||||||
|
@ -76,7 +76,7 @@ FindConnectedComponentNew.prototype.calculate = function(fillUpText = false)
|
|||||||
for (j = 0; j < connectedVertex[stackElement.id].length; j++)
|
for (j = 0; j < connectedVertex[stackElement.id].length; j++)
|
||||||
{
|
{
|
||||||
var nextVertex = connectedVertex[stackElement.id][j];
|
var nextVertex = connectedVertex[stackElement.id][j];
|
||||||
var connectedEdge = this.graph.FindEdge(stackElement.id, nextVertex.id);
|
var connectedEdge = this.graph.FindEdgeAny(stackElement.id, nextVertex.id);
|
||||||
if (stack.indexOf(nextVertex) < 0)
|
if (stack.indexOf(nextVertex) < 0)
|
||||||
{
|
{
|
||||||
stack.push(nextVertex);
|
stack.push(nextVertex);
|
||||||
@ -113,6 +113,12 @@ FindConnectedComponentNew.prototype.getPriority = function()
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Algorithm support multi graph
|
||||||
|
FindConnectedComponentNew.prototype.IsSupportMultiGraph = function()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Factory for connected components.
|
// Factory for connected components.
|
||||||
function CreateConnectedComponetsNew(graph, app)
|
function CreateConnectedComponetsNew(graph, app)
|
||||||
|
@ -91,7 +91,7 @@ DFSAlgorithm.prototype.dfs = function(vertex, vertexArray, edgeArray)
|
|||||||
for (var i = 0; i < this.graph.vertices.length; i ++)
|
for (var i = 0; i < this.graph.vertices.length; i ++)
|
||||||
{
|
{
|
||||||
var nextVertex = this.graph.vertices[i];
|
var nextVertex = this.graph.vertices[i];
|
||||||
var edge = this.graph.FindEdge(vertex.id, nextVertex.id);
|
var edge = this.graph.FindEdgeAny(vertex.id, nextVertex.id);
|
||||||
if (edge && !vertexArray.includes(nextVertex))
|
if (edge && !vertexArray.includes(nextVertex))
|
||||||
{
|
{
|
||||||
edgeArray.push(edge);
|
edgeArray.push(edge);
|
||||||
@ -103,6 +103,12 @@ DFSAlgorithm.prototype.dfs = function(vertex, vertexArray, edgeArray)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Algorithm support multi graph
|
||||||
|
DFSAlgorithm.prototype.IsSupportMultiGraph = function()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Factory for connected components.
|
// Factory for connected components.
|
||||||
function CreateDFSAlgorithm(graph, app)
|
function CreateDFSAlgorithm(graph, app)
|
||||||
{
|
{
|
||||||
|
@ -103,7 +103,7 @@ FloidAlgorithm.prototype.resultMatrix = function()
|
|||||||
for (var j = 0; j < this.graph.vertices.length; j ++)
|
for (var j = 0; j < this.graph.vertices.length; j ++)
|
||||||
{
|
{
|
||||||
var v2 = this.graph.vertices[j];
|
var v2 = this.graph.vertices[j];
|
||||||
var edge = this.graph.FindEdge(v1.id, v2.id);
|
var edge = this.graph.FindEdgeMin(v1.id, v2.id);
|
||||||
if (edge != null)
|
if (edge != null)
|
||||||
{
|
{
|
||||||
this.matrix[i][j] = edge.GetWeight();
|
this.matrix[i][j] = edge.GetWeight();
|
||||||
|
@ -159,6 +159,11 @@ GraphReorder.prototype.getPriority = function()
|
|||||||
return -8.5;
|
return -8.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Algorithm support multi graph
|
||||||
|
GraphReorder.prototype.IsSupportMultiGraph = function()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Factory for connected components.
|
// Factory for connected components.
|
||||||
function CreateAlgorithmGraphReorder(graph, app)
|
function CreateAlgorithmGraphReorder(graph, app)
|
||||||
|
@ -104,7 +104,7 @@ MinimumSpanningTree.prototype.resultStartedFrom = function(vertex, connectedVert
|
|||||||
for (j = 0; j < connectedVertex[element.id].length; j++)
|
for (j = 0; j < connectedVertex[element.id].length; j++)
|
||||||
{
|
{
|
||||||
var connectedElement = connectedVertex[element.id][j];
|
var connectedElement = connectedVertex[element.id][j];
|
||||||
var connectedEdge = this.graph.FindEdge(element.id, connectedElement.id);
|
var connectedEdge = this.graph.FindEdgeMin(element.id, connectedElement.id);
|
||||||
if (inTree.indexOf(connectedElement) < 0)
|
if (inTree.indexOf(connectedElement) < 0)
|
||||||
{
|
{
|
||||||
if (minEdge == null || minEdge.weight > connectedEdge.weight)
|
if (minEdge == null || minEdge.weight > connectedEdge.weight)
|
||||||
|
@ -182,7 +182,6 @@ ModernGraphStyle.prototype.getObjectSelectedGroup = function(object)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Factory for connected components.
|
// Factory for connected components.
|
||||||
function CreateAlgorithmModernGraphStyle(graph, app)
|
function CreateAlgorithmModernGraphStyle(graph, app)
|
||||||
{
|
{
|
||||||
|
@ -183,7 +183,7 @@ RadiusAndDiameter.prototype.getPathByMatrix = function(adjacencyMatrix, minPathM
|
|||||||
if (minPathMatrix[i][finishNode] == length - adjacencyMatrix[startNode][i] && i != startNode)
|
if (minPathMatrix[i][finishNode] == length - adjacencyMatrix[startNode][i] && i != startNode)
|
||||||
{
|
{
|
||||||
res.push(vertices[startNode]);
|
res.push(vertices[startNode]);
|
||||||
res.push(this.graph.FindEdge(vertices[startNode].id, vertices[i].id));
|
res.push(this.graph.FindEdgeMin(vertices[startNode].id, vertices[i].id));
|
||||||
|
|
||||||
length -= adjacencyMatrix[startNode][i];
|
length -= adjacencyMatrix[startNode][i];
|
||||||
startNode = i;
|
startNode = i;
|
||||||
@ -193,7 +193,7 @@ RadiusAndDiameter.prototype.getPathByMatrix = function(adjacencyMatrix, minPathM
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.push(vertices[startNode]);
|
res.push(vertices[startNode]);
|
||||||
res.push(this.graph.FindEdge(vertices[startNode].id, vertices[finishNode].id));
|
res.push(this.graph.FindEdgeMin(vertices[startNode].id, vertices[finishNode].id));
|
||||||
res.push(vertices[finishNode]);
|
res.push(vertices[finishNode]);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
@ -104,6 +104,9 @@ var g_vertexDraw = "Vertex draw style";
|
|||||||
var g_edgeDraw = "Edge draw style";
|
var g_edgeDraw = "Edge draw style";
|
||||||
var g_backgroundStyle = "Bacgkround style";
|
var g_backgroundStyle = "Bacgkround style";
|
||||||
|
|
||||||
|
var g_GrapsIsMultiMessage = "Graph is multigraph";
|
||||||
|
var g_GrapsIsGeneralMessage = "";
|
||||||
|
|
||||||
function loadTexts()
|
function loadTexts()
|
||||||
{
|
{
|
||||||
g_textsSelectAndMove = document.getElementById("SelectAndMoveObject").innerHTML;
|
g_textsSelectAndMove = document.getElementById("SelectAndMoveObject").innerHTML;
|
||||||
@ -208,4 +211,7 @@ function loadTexts()
|
|||||||
g_edgeDraw = document.getElementById("edgeDrawStyle").innerHTML;
|
g_edgeDraw = document.getElementById("edgeDrawStyle").innerHTML;
|
||||||
|
|
||||||
g_backgroundStyle = document.getElementById("backgroundStyle").innerHTML;
|
g_backgroundStyle = document.getElementById("backgroundStyle").innerHTML;
|
||||||
|
|
||||||
|
g_GrapsIsMultiMessage = document.getElementById("graphIsMultiMessage").innerHTML;
|
||||||
|
g_GrapsIsGeneralMessage = document.getElementById("graphIsGeneralMessage").innerHTML;
|
||||||
}
|
}
|
||||||
|
18
tpl/home.php
18
tpl/home.php
@ -150,6 +150,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="canvasSection">
|
<section id="canvasSection">
|
||||||
|
<span id="CanvasMessage"></span>
|
||||||
<button type="button" class="btn btn-default btn-sm hidden-phone" id="Fullscreen"><span class="glyphicon glyphicon-resize-full fa-fw" id="FullscreenIcon"></span></button>
|
<button type="button" class="btn btn-default btn-sm hidden-phone" id="Fullscreen"><span class="glyphicon glyphicon-resize-full fa-fw" id="FullscreenIcon"></span></button>
|
||||||
<canvas id="canvas"><?= L('browser_no_support')?></canvas>
|
<canvas id="canvas"><?= L('browser_no_support')?></canvas>
|
||||||
<div id="developerTools" class="well well-sm">
|
<div id="developerTools" class="well well-sm">
|
||||||
@ -221,6 +222,20 @@
|
|||||||
<span onClick="document.getElementById('EdgeWeight').value='7'; document.getElementById('EdgeWeightSlider').value=7;" style="cursor: pointer" class="defaultWeigth">7</span>
|
<span onClick="document.getElementById('EdgeWeight').value='7'; document.getElementById('EdgeWeightSlider').value=7;" style="cursor: pointer" class="defaultWeigth">7</span>
|
||||||
<span onClick="document.getElementById('EdgeWeight').value='11'; document.getElementById('EdgeWeightSlider').value=11;" style="cursor: pointer" class="defaultWeigth">11</span>
|
<span onClick="document.getElementById('EdgeWeight').value='11'; document.getElementById('EdgeWeightSlider').value=11;" style="cursor: pointer" class="defaultWeigth">11</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="NewEdgeAction">
|
||||||
|
<div class="InlineStyle PaddingRight">
|
||||||
|
<input class="form-check-input" type="radio" name="NewEdgeActionValue" id="RadiosReplaceEdge" value="replace" checked>
|
||||||
|
<label for="RadiosReplaceEdge">
|
||||||
|
<?= L('replace_edge')?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="InlineStyle PaddingRight">
|
||||||
|
<input class="form-check-input" type="radio" name="NewEdgeActionValue" id="RadiosAddEdge" value="add">
|
||||||
|
<label for="RadiosAddEdge" id="RadiosAddEdgeLabel">
|
||||||
|
<?= L('add_edge')?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -248,6 +263,7 @@
|
|||||||
<form>
|
<form>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<p><?= L('adjacency_matrix_description')?></p>
|
<p><?= L('adjacency_matrix_description')?></p>
|
||||||
|
<p id="AdjacencyMatrixMultiGraphDesc"><?= L('adjacency_matrix_multigraph_description')?></p>
|
||||||
<textarea name="adjacencyMatrixField" id="AdjacencyMatrixField" wrap="off"></textarea>
|
<textarea name="adjacencyMatrixField" id="AdjacencyMatrixField" wrap="off"></textarea>
|
||||||
<p id="BadMatrixFormatMessage"><?= L('adjacency_matrix_bad_format')?></p>
|
<p id="BadMatrixFormatMessage"><?= L('adjacency_matrix_bad_format')?></p>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@ -551,6 +567,8 @@
|
|||||||
<p id="edgeDrawStyle" class="translation"><?= L('edge_draw_style')?></p>
|
<p id="edgeDrawStyle" class="translation"><?= L('edge_draw_style')?></p>
|
||||||
<p id="backgroundStyle" class="translation"><?= L('background_style')?></p>
|
<p id="backgroundStyle" class="translation"><?= L('background_style')?></p>
|
||||||
|
|
||||||
|
<p id="graphIsMultiMessage" class="translation"><?= L('graph_is_multi_message')?></p>
|
||||||
|
<p id="graphIsGeneralMessage" class="translation"><?= L('graph_is_general_message')?></p>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
<!--
|
<!--
|
||||||
|
Loading…
x
Reference in New Issue
Block a user