graphviz: rank a group of nodes to the right from another group of nodes - graphviz

I'm trying to separate all the green nodes in the image below to the right of the red nodes, as if there was an imaginary vertical line between them.
This is the current input file:
digraph G {
rankdir="LR"
subgraph cluster_2 {
"astn-serialize-string-res" [ color="green" ]
}
subgraph cluster_4 {
"pareto-lang-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_5 {
graph[color="red"]
"astn-unmarshall-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_6 {
graph[color="red"]
"astn-typedhandlers-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_7 {
graph[color="red"]
"astn-tokenizer-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_8 {
"astn-serializer-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_9 {
graph[color="red"]
"astn-parser-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_10 {
graph[color="red"]
"astn-mrshlschema-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_11 {
graph[color="red"]
"astn-marshall-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_12 {
graph[color="red"]
"astn-loader-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_13 {
graph[color="red"]
"astn-ide-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_14 {
graph[color="red"]
"astn-expect-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_16 {
"astn-typedhandlers-api" [ color="green" ]
}
subgraph cluster_17 {
"astn-tokenconsumer-api" [ color="green" ]
}
subgraph cluster_18 {
"astn-serialize-string-api" [ color="green" ]
}
subgraph cluster_19 {
"astn-handlers-api" [ color="green" ]
}
"pareto-lang-lib--fountain-pen-lib" [label= "fountain-pen-lib"]
"pareto-lang-lib" -> "pareto-lang-lib--fountain-pen-lib"
"pareto-lang-lib" -> "astn-handlers-api"
"pareto-lang-lib" -> "astn-expect-lib"
"astn-unmarshall-lib" -> "astn-typedhandlers-api"
"astn-unmarshall-lib" -> "astn-handlers-api"
"astn-typedhandlers-lib" -> "astn-typedhandlers-api"
"astn-typedhandlers-lib" -> "astn-tokenconsumer-api"
"astn-typedhandlers-lib" -> "astn-handlers-api"
"astn-typedhandlers-lib" -> "astn-expect-lib"
"astn-typedhandlers-api" -> "astn-handlers-api"
"astn-tokenizer-lib" -> "astn-tokenconsumer-api"
"astn-serializer-lib" -> "astn-serialize-string-api"
"astn-serializer-lib" -> "astn-handlers-api"
"astn-serialize-string-res" -> "astn-serialize-string-api"
"astn-parser-lib" -> "astn-tokenconsumer-api"
"astn-parser-lib" -> "astn-handlers-api"
"astn-mrshlschema-lib" -> "pareto-lang-lib"
"astn-mrshlschema-lib" -> "astn-typedhandlers-api"
"astn-mrshlschema-lib" -> "astn-handlers-api"
"astn-marshall-lib" -> "astn-typedhandlers-api"
"astn-marshall-lib" -> "astn-tokenconsumer-api"
"astn-marshall-lib" -> "astn-parser-lib"
"astn-loader-lib" -> "astn-unmarshall-lib"
"astn-loader-lib" -> "astn-tokenizer-lib"
"astn-loader-lib" -> "astn-parser-lib"
"astn-loader-lib" -> "astn-mrshlschema-lib"
"astn-loader-lib" -> "astn-expect-lib"
"astn-ide-lib" -> "astn-typedhandlers-api"
"astn-expect-lib" -> "astn-handlers-api"
}
I thought that adding clusters would solve the trick, but it messes up the layout:
This is the input with clusters:
digraph G {
rankdir="LR"
subgraph cluster_0 {
}
subgraph cluster_1 {
subgraph cluster_2 {
"astn-serialize-string-res" [ color="green" ]
}
}
subgraph cluster_3 {
subgraph cluster_4 {
"pareto-lang-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_5 {
graph[color="red"]
"astn-unmarshall-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_6 {
graph[color="red"]
"astn-typedhandlers-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_7 {
graph[color="red"]
"astn-tokenizer-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_8 {
"astn-serializer-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_9 {
graph[color="red"]
"astn-parser-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_10 {
graph[color="red"]
"astn-mrshlschema-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_11 {
graph[color="red"]
"astn-marshall-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_12 {
graph[color="red"]
"astn-loader-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_13 {
graph[color="red"]
"astn-ide-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_14 {
graph[color="red"]
"astn-expect-lib" [ color="red", penwidth=3 ]
}
}
subgraph cluster_15 {
subgraph cluster_16 {
"astn-typedhandlers-api" [ color="green" ]
}
subgraph cluster_17 {
"astn-tokenconsumer-api" [ color="green" ]
}
subgraph cluster_18 {
"astn-serialize-string-api" [ color="green" ]
}
subgraph cluster_19 {
"astn-handlers-api" [ color="green" ]
}
}
"pareto-lang-lib--fountain-pen-lib" [label= "fountain-pen-lib"]
"pareto-lang-lib" -> "pareto-lang-lib--fountain-pen-lib"
"pareto-lang-lib" -> "astn-handlers-api"
"pareto-lang-lib" -> "astn-expect-lib"
"astn-unmarshall-lib" -> "astn-typedhandlers-api"
"astn-unmarshall-lib" -> "astn-handlers-api"
"astn-typedhandlers-lib" -> "astn-typedhandlers-api"
"astn-typedhandlers-lib" -> "astn-tokenconsumer-api"
"astn-typedhandlers-lib" -> "astn-handlers-api"
"astn-typedhandlers-lib" -> "astn-expect-lib"
"astn-typedhandlers-api" -> "astn-handlers-api"
"astn-tokenizer-lib" -> "astn-tokenconsumer-api"
"astn-serializer-lib" -> "astn-serialize-string-api"
"astn-serializer-lib" -> "astn-handlers-api"
"astn-serialize-string-res" -> "astn-serialize-string-api"
"astn-parser-lib" -> "astn-tokenconsumer-api"
"astn-parser-lib" -> "astn-handlers-api"
"astn-mrshlschema-lib" -> "pareto-lang-lib"
"astn-mrshlschema-lib" -> "astn-typedhandlers-api"
"astn-mrshlschema-lib" -> "astn-handlers-api"
"astn-marshall-lib" -> "astn-typedhandlers-api"
"astn-marshall-lib" -> "astn-tokenconsumer-api"
"astn-marshall-lib" -> "astn-parser-lib"
"astn-loader-lib" -> "astn-unmarshall-lib"
"astn-loader-lib" -> "astn-tokenizer-lib"
"astn-loader-lib" -> "astn-parser-lib"
"astn-loader-lib" -> "astn-mrshlschema-lib"
"astn-loader-lib" -> "astn-expect-lib"
"astn-ide-lib" -> "astn-typedhandlers-api"
"astn-expect-lib" -> "astn-handlers-api"
}
is there a way to achieve what I want?

Here is a solution using rank=same "backwards" (figuratively). Usually rank=same is part of the graph from the beginning, here we "eyeball" the original graph and guess at two new subgraphs that will produce the graph we want. Pretty easy in this case, but a different, larger graph could have been much harder.
digraph G {
rankdir="LR"
subgraph cluster_2 {
"astn-serialize-string-res" [ color="green" ]
}
subgraph cluster_4 {
"pareto-lang-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_5 {
graph[color="red"]
"astn-unmarshall-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_6 {
graph[color="red"]
"astn-typedhandlers-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_7 {
graph[color="red"]
"astn-tokenizer-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_8 {
"astn-serializer-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_9 {
graph[color="red"]
"astn-parser-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_10 {
graph[color="red"]
"astn-mrshlschema-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_11 {
graph[color="red"]
"astn-marshall-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_12 {
graph[color="red"]
"astn-loader-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_13 {
graph[color="red"]
"astn-ide-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_14 {
graph[color="red"]
"astn-expect-lib" [ color="red", penwidth=3 ]
}
subgraph cluster_16 {
"astn-typedhandlers-api" [ color="green" ]
}
subgraph cluster_17 {
"astn-tokenconsumer-api" [ color="green" ]
}
subgraph cluster_18 {
"astn-serialize-string-api" [ color="green" ]
}
subgraph cluster_19 {
"astn-handlers-api" [ color="green" ]
}
{rank=same
"astn-unmarshall-lib" "astn-expect-lib" "astn-serializer-lib"
}
{rank=same
"astn-tokenconsumer-api" "astn-typedhandlers-api" "astn-serialize-string-api" "astn-serialize-string-res"
}
"pareto-lang-lib--fountain-pen-lib" [label= "fountain-pen-lib"]
"pareto-lang-lib" -> "pareto-lang-lib--fountain-pen-lib"
"pareto-lang-lib" -> "astn-handlers-api"
"pareto-lang-lib" -> "astn-expect-lib"
"astn-unmarshall-lib" -> "astn-typedhandlers-api"
"astn-unmarshall-lib" -> "astn-handlers-api"
"astn-typedhandlers-lib" -> "astn-typedhandlers-api"
"astn-typedhandlers-lib" -> "astn-tokenconsumer-api"
"astn-typedhandlers-lib" -> "astn-handlers-api"
"astn-typedhandlers-lib" -> "astn-expect-lib"
"astn-typedhandlers-api" -> "astn-handlers-api"
"astn-tokenizer-lib" -> "astn-tokenconsumer-api"
"astn-serializer-lib" -> "astn-serialize-string-api"
"astn-serializer-lib" -> "astn-handlers-api"
"astn-serialize-string-res" -> "astn-serialize-string-api"
"astn-parser-lib" -> "astn-tokenconsumer-api"
"astn-parser-lib" -> "astn-handlers-api"
"astn-mrshlschema-lib" -> "pareto-lang-lib"
"astn-mrshlschema-lib" -> "astn-typedhandlers-api"
"astn-mrshlschema-lib" -> "astn-handlers-api"
"astn-marshall-lib" -> "astn-typedhandlers-api"
"astn-marshall-lib" -> "astn-tokenconsumer-api"
"astn-marshall-lib" -> "astn-parser-lib"
"astn-loader-lib" -> "astn-unmarshall-lib"
"astn-loader-lib" -> "astn-tokenizer-lib"
"astn-loader-lib" -> "astn-parser-lib"
"astn-loader-lib" -> "astn-mrshlschema-lib"
"astn-loader-lib" -> "astn-expect-lib"
"astn-ide-lib" -> "astn-typedhandlers-api"
"astn-expect-lib" -> "astn-handlers-api"
}
Giving:

Related

How do I group category properties within an infinite subcategory

I have a collection like below.
Including all nested subcategories of this collection.
I want to group by "type" column under all "categoryprop".
How can I do it?
[
{
"id":1,
"parent_id":null,
"order":0,
"name":"Home Page",
"slug":"\/",
"type":"home-page",
"status":"1",
"menu":"0",
"megamenu":"0",
"megamcol":0,
"megamrow":0,
"menuheadershow":"0",
"menufootershow":"0",
"menuiconbg":"",
"menuicon":"",
"created_at":null,
"updated_at":null,
"subcategory":[
],
"categoryprop":[
]
},
{
"id":2,
"parent_id":null,
"order":1,
"name":"Hosting",
"slug":"hosting",
"type":"hosting",
"status":"1",
"menu":"0",
"megamenu":"1",
"megamcol":2,
"megamrow":4,
"menuheadershow":"1",
"menufootershow":"1",
"menuiconbg":null,
"menuicon":null,
"created_at":null,
"updated_at":null,
"subcategory":[
{
"id":3,
"parent_id":2,
"order":0,
"name":"Web Hosting",
"slug":"web-hosting",
"type":"hosting",
"status":"1",
"menu":"0",
"megamenu":"0",
"megamcol":0,
"megamrow":0,
"menuheadershow":"0",
"menufootershow":"0",
"menuiconbg":"primary-bg",
"menuicon":"fas fa-server",
"created_at":null,
"updated_at":null,
"subcategory":[
{
"id":15,
"parent_id":3,
"order":0,
"name":"Web Hosting",
"slug":"web-hosting",
"type":"hosting",
"status":"1",
"menu":"0",
"megamenu":"0",
"megamcol":0,
"megamrow":0,
"menuheadershow":"0",
"menufootershow":"0",
"menuiconbg":"primary-bg",
"menuicon":"fas fa-server",
"created_at":null,
"updated_at":null,
"subcategory":[
],
"categoryprop":[
{
"id":3,
"category_id":15,
"order":0,
"type":"menudescription",
"name":"Men\u00fc A\u00e7\u0131klamas\u0131",
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"categorycontent":[
{
"id":5,
"categoryprop_id":3,
"title":"Bu bir denemedir",
"content":"Bu bir deneme",
"image":null,
"lang_id":1,
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"lang":{
"id":1,
"name":"T\u00fcrk\u00e7e",
"symbol":"TR",
"code":"tr",
"default":1,
"created_at":null,
"updated_at":null
}
},
{
"id":6,
"categoryprop_id":3,
"title":"Bur bir deneme",
"content":"Bu bir deneme",
"image":null,
"lang_id":2,
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"lang":{
"id":2,
"name":"English",
"symbol":"EN",
"code":"en_us",
"default":0,
"created_at":null,
"updated_at":null
}
}
]
},
{
"id":4,
"category_id":3,
"order":0,
"type":"menudescription",
"name":"Men\u00fc A\u00e7\u0131klamas\u01312",
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"categorycontent":[
{
"id":7,
"categoryprop_id":4,
"title":"Bu bir denemedir",
"content":"Bu bir deneme",
"image":null,
"lang_id":1,
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"lang":{
"id":1,
"name":"T\u00fcrk\u00e7e",
"symbol":"TR",
"code":"tr",
"default":1,
"created_at":null,
"updated_at":null
}
},
{
"id":8,
"categoryprop_id":4,
"title":"Bur bir deneme",
"content":"Bu bir deneme",
"image":null,
"lang_id":2,
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"lang":{
"id":2,
"name":"English",
"symbol":"EN",
"code":"en_us",
"default":0,
"created_at":null,
"updated_at":null
}
}
]
}
]
}
],
"categoryprop":[
{
"id":1,
"category_id":3,
"order":0,
"type":"menudescription",
"name":"Men\u00fc A\u00e7\u0131klamas\u0131",
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"categorycontent":[
{
"id":1,
"categoryprop_id":1,
"title":"Bu bir denemedir",
"content":"Bu bir deneme",
"image":null,
"lang_id":1,
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"lang":{
"id":1,
"name":"T\u00fcrk\u00e7e",
"symbol":"TR",
"code":"tr",
"default":1,
"created_at":null,
"updated_at":null
}
},
{
"id":2,
"categoryprop_id":1,
"title":"Bur bir deneme",
"content":"Bu bir deneme",
"image":null,
"lang_id":2,
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"lang":{
"id":2,
"name":"English",
"symbol":"EN",
"code":"en_us",
"default":0,
"created_at":null,
"updated_at":null
}
}
]
},
{
"id":2,
"category_id":3,
"order":0,
"type":"menudescription",
"name":"Men\u00fc A\u00e7\u0131klamas\u01312",
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"categorycontent":[
{
"id":3,
"categoryprop_id":2,
"title":"Bu bir denemedir",
"content":"Bu bir deneme",
"image":null,
"lang_id":1,
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"lang":{
"id":1,
"name":"T\u00fcrk\u00e7e",
"symbol":"TR",
"code":"tr",
"default":1,
"created_at":null,
"updated_at":null
}
},
{
"id":4,
"categoryprop_id":2,
"title":"Bur bir deneme",
"content":"Bu bir deneme",
"image":null,
"lang_id":2,
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"lang":{
"id":2,
"name":"English",
"symbol":"EN",
"code":"en_us",
"default":0,
"created_at":null,
"updated_at":null
}
}
]
}
]
},
{
"id":4,
"parent_id":2,
"order":1,
"name":"Wordpress Hosting",
"slug":"wordpress-hosting",
"type":"hosting",
"status":"1",
"menu":"0",
"megamenu":"0",
"megamcol":0,
"megamrow":0,
"menuheadershow":"0",
"menufootershow":"0",
"menuiconbg":"accent-bg",
"menuicon":"fas fa-box",
"created_at":null,
"updated_at":null,
"subcategory":[
],
"categoryprop":[
]
},
{
"id":5,
"parent_id":2,
"order":2,
"name":"Linux Hosting",
"slug":"linux-hosting",
"type":"hosting",
"status":"1",
"menu":"0",
"megamenu":"0",
"megamcol":0,
"megamrow":0,
"menuheadershow":"0",
"menufootershow":"0",
"menuiconbg":"secondary-bg",
"menuicon":"fas fa-cloud",
"created_at":null,
"updated_at":null,
"subcategory":[
],
"categoryprop":[
]
},
{
"id":6,
"parent_id":2,
"order":3,
"name":"S\u0131n\u0131rs\u0131z Hosting",
"slug":"sinirsiz-hosting",
"type":"hosting",
"status":"1",
"menu":"0",
"megamenu":"0",
"megamcol":0,
"megamrow":0,
"menuheadershow":"0",
"menufootershow":"0",
"menuiconbg":"twitter-bg",
"menuicon":"fas fa-infinity",
"created_at":null,
"updated_at":null,
"subcategory":[
],
"categoryprop":[
]
}
]
},
]
The result I expected:
[
{
"id":1,
"parent_id":null,
"order":0,
"name":"Home Page",
"slug":"\/",
"type":"home-page",
"status":"1",
"menu":"0",
"megamenu":"0",
"megamcol":0,
"megamrow":0,
"menuheadershow":"0",
"menufootershow":"0",
"menuiconbg":"",
"menuicon":"",
"created_at":null,
"updated_at":null,
"subcategory":[
],
"categoryprop":[
]
},
{
"id":2,
"parent_id":null,
"order":1,
"name":"Hosting",
"slug":"hosting",
"type":"hosting",
"status":"1",
"menu":"0",
"megamenu":"1",
"megamcol":2,
"megamrow":4,
"menuheadershow":"1",
"menufootershow":"1",
"menuiconbg":null,
"menuicon":null,
"created_at":null,
"updated_at":null,
"subcategory":[
{
"id":3,
"parent_id":2,
"order":0,
"name":"Web Hosting",
"slug":"web-hosting",
"type":"hosting",
"status":"1",
"menu":"0",
"megamenu":"0",
"megamcol":0,
"megamrow":0,
"menuheadershow":"0",
"menufootershow":"0",
"menuiconbg":"primary-bg",
"menuicon":"fas fa-server",
"created_at":null,
"updated_at":null,
"subcategory":[
{
"id":15,
"parent_id":3,
"order":0,
"name":"Web Hosting",
"slug":"web-hosting",
"type":"hosting",
"status":"1",
"menu":"0",
"megamenu":"0",
"megamcol":0,
"megamrow":0,
"menuheadershow":"0",
"menufootershow":"0",
"menuiconbg":"primary-bg",
"menuicon":"fas fa-server",
"created_at":null,
"updated_at":null,
"subcategory":[
],
"categoryprop":[
{
"menudescription":[
{
"id":3,
"category_id":15,
"order":0,
"type":"menudescription",
"name":"Men\u00fc A\u00e7\u0131klamas\u0131",
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"categorycontent":[
{
"id":5,
"categoryprop_id":3,
"title":"Bu bir denemedir",
"content":"Bu bir deneme",
"image":null,
"lang_id":1,
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"lang":{
"id":1,
"name":"T\u00fcrk\u00e7e",
"symbol":"TR",
"code":"tr",
"default":1,
"created_at":null,
"updated_at":null
}
},
{
"id":6,
"categoryprop_id":3,
"title":"Bur bir deneme",
"content":"Bu bir deneme",
"image":null,
"lang_id":2,
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"lang":{
"id":2,
"name":"English",
"symbol":"EN",
"code":"en_us",
"default":0,
"created_at":null,
"updated_at":null
}
}
]
},
{
"id":4,
"category_id":3,
"order":0,
"type":"menudescription",
"name":"Men\u00fc A\u00e7\u0131klamas\u01312",
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"categorycontent":[
{
"id":7,
"categoryprop_id":4,
"title":"Bu bir denemedir",
"content":"Bu bir deneme",
"image":null,
"lang_id":1,
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"lang":{
"id":1,
"name":"T\u00fcrk\u00e7e",
"symbol":"TR",
"code":"tr",
"default":1,
"created_at":null,
"updated_at":null
}
},
{
"id":8,
"categoryprop_id":4,
"title":"Bur bir deneme",
"content":"Bu bir deneme",
"image":null,
"lang_id":2,
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"lang":{
"id":2,
"name":"English",
"symbol":"EN",
"code":"en_us",
"default":0,
"created_at":null,
"updated_at":null
}
}
]
}
]
}
]
}
],
"categoryprop":[
{
"menudescription":[
{
"id":1,
"category_id":3,
"order":0,
"type":"menudescription",
"name":"Men\u00fc A\u00e7\u0131klamas\u0131",
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"categorycontent":[
{
"id":1,
"categoryprop_id":1,
"title":"Bu bir denemedir",
"content":"Bu bir deneme",
"image":null,
"lang_id":1,
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"lang":{
"id":1,
"name":"T\u00fcrk\u00e7e",
"symbol":"TR",
"code":"tr",
"default":1,
"created_at":null,
"updated_at":null
}
},
{
"id":2,
"categoryprop_id":1,
"title":"Bur bir deneme",
"content":"Bu bir deneme",
"image":null,
"lang_id":2,
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"lang":{
"id":2,
"name":"English",
"symbol":"EN",
"code":"en_us",
"default":0,
"created_at":null,
"updated_at":null
}
}
]
},
{
"id":2,
"category_id":3,
"order":0,
"type":"menudescription",
"name":"Men\u00fc A\u00e7\u0131klamas\u01312",
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"categorycontent":[
{
"id":3,
"categoryprop_id":2,
"title":"Bu bir denemedir",
"content":"Bu bir deneme",
"image":null,
"lang_id":1,
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"lang":{
"id":1,
"name":"T\u00fcrk\u00e7e",
"symbol":"TR",
"code":"tr",
"default":1,
"created_at":null,
"updated_at":null
}
},
{
"id":4,
"categoryprop_id":2,
"title":"Bur bir deneme",
"content":"Bu bir deneme",
"image":null,
"lang_id":2,
"created_at":"2022-09-09T16:37:02.000000Z",
"updated_at":"2022-09-09T16:37:02.000000Z",
"lang":{
"id":2,
"name":"English",
"symbol":"EN",
"code":"en_us",
"default":0,
"created_at":null,
"updated_at":null
}
}
]
}
]
}
]
},
{
"id":4,
"parent_id":2,
"order":1,
"name":"Wordpress Hosting",
"slug":"wordpress-hosting",
"type":"hosting",
"status":"1",
"menu":"0",
"megamenu":"0",
"megamcol":0,
"megamrow":0,
"menuheadershow":"0",
"menufootershow":"0",
"menuiconbg":"accent-bg",
"menuicon":"fas fa-box",
"created_at":null,
"updated_at":null,
"subcategory":[
],
"categoryprop":[
]
},
{
"id":5,
"parent_id":2,
"order":2,
"name":"Linux Hosting",
"slug":"linux-hosting",
"type":"hosting",
"status":"1",
"menu":"0",
"megamenu":"0",
"megamcol":0,
"megamrow":0,
"menuheadershow":"0",
"menufootershow":"0",
"menuiconbg":"secondary-bg",
"menuicon":"fas fa-cloud",
"created_at":null,
"updated_at":null,
"subcategory":[
],
"categoryprop":[
]
},
{
"id":6,
"parent_id":2,
"order":3,
"name":"S\u0131n\u0131rs\u0131z Hosting",
"slug":"sinirsiz-hosting",
"type":"hosting",
"status":"1",
"menu":"0",
"megamenu":"0",
"megamcol":0,
"megamrow":0,
"menuheadershow":"0",
"menufootershow":"0",
"menuiconbg":"twitter-bg",
"menuicon":"fas fa-infinity",
"created_at":null,
"updated_at":null,
"subcategory":[
],
"categoryprop":[
]
}
]
},
{
"id":11,
"parent_id":null,
"order":1,
"name":"Alan Ad\u0131",
"slug":"alanadi",
"type":"alanadi",
"status":"1",
"menu":"0",
"megamenu":"0",
"megamcol":0,
"megamrow":0,
"menuheadershow":"0",
"menufootershow":"0",
"menuiconbg":null,
"menuicon":null,
"created_at":null,
"updated_at":null,
"subcategory":[
],
"categoryprop":[
]
}
]
First of all, the array is too complicated. I think you should simplify it somehow.
I have two solution methods. These are the Laravel way and the pure PHP way.
Method-1 (Laravel)
Before starting this, you must ensure that values of the "subcategory and categoryprop" are instances of the Illuminate\Support\Collection
. If they are not an instance of a Collection object, use Method-2.
class Method1
{
public function handle() {
// ...
var_dump($categories->map($this, 'mapper'));
}
public function mapper($collection) {
// If it has sub-categories, map them
if (optional($collection['subcategory'])->count()) {
$collection['subcategory'] = $collection['subcategory']->map([$this, 'mapper']);
}
$lastItemIndex = $collection->count() - 1;
// If it is an array of categories, map them.
if (isset($collection[$lastItemIndex])) {
return $collection->map([$this, 'mapper']);
}
// If there is no item in $collection['categoryprop']
if (0 >= optional($collection->get('categoryprop'))->count()) {
return $collection;
}
$collection['categoryprop'] = $collection['categoryprop']->groupBy('type');
return $collection;
}
}
Method-2 (Pure PHP)
class Method2
{
public function handle() {
// ...
var_dump($this->groupByCategoryProp($categories));
}
public function groupByItemKey($collection, $key) {
$generatedList = [];
foreach ($collection as $item) {
$type = $item[$key];
$generatedList[$type][] = $item;
}
return $generatedList;
}
public function groupByCategoryProp($collection) {
// If it has sub-categories, map them.
if (isset($collection['subcategory']) && 0 < count($collection['subcategory']) ) {
$subCategory = $this->groupByCategoryProp($collection['subcategory']);
$collection['subcategory'] = $subCategory;
}
$lastItemIndex = count($collection) - 1;
// If it is an array of categories, map them.
if (isset($collection[$lastItemIndex])) {
foreach ($collection as $i => $category) {
$collection[$i] = $this->groupByCategoryProp($category);
}
return $collection;
}
// If there is no categoryprop
if (! isset($collection['categoryprop'])) {
return $collection;
}
// If there is no item in $collection['categoryprop']
if (0 === count($collection['categoryprop']) ) {
return $collection;
}
$collection['categoryprop'] = $this->groupByItemKey($collection['categoryprop'], 'type') ;
return $collection;
}
}

How to group colllection by children in Laravel?

I have a collection "features" that each has a "feature group". 1 Feature belongsToMany feature groups.
1 feature belongsToMany cars, so it's a subcollection on itself. In my car view I want to group the features. How would I go about this?
An example collection:
[
{
"id":1,
"group_id":1,
"name":"metallic",
"pivot":{
"car_id":1,
"feature_id":1
},
"group":{
"id":1,
"name":"paint"
}
},
{
"id":2,
"group_id":1,
"name":"blue",
"pivot":{
"car_id":1,
"feature_id":2
},
"group":{
"id":1,
"name":"paint"
}
},
{
"id":5,
"group_id":2,
"name":"gps",
"pivot":{
"car_id":1,
"feature_id":5
},
"group":{
"id":2,
"name":"interior"
}
},
{
"id":6,
"group_id":2,
"name":"leather seats",
"pivot":{
"car_id":1,
"feature_id":6
},
"group":{
"id":2,
"name":"interior"
}
}
]
The desired result would be something like this:
[
{
"id":1,
"name":"paint",
"features":[
{
"id":1,
"group_id":1,
"name":"metallic"
},
{
"id":2,
"group_id":1,
"name":"blue"
}
]
},
{
"id":2,
"name":"interior",
"features":[
{
"id":5,
"group_id":2,
"name":"gps"
},
{
"id":6,
"group_id":2,
"name":"leather seats"
}
]
}
]
In my Controller I am trying to create a new collection, using each() loop and mergeRecursive(), like this:
$car = Car::where('slug', $slug)->first()->load('features.group');
$newCollection = collect(["group 1" => "feature 1"]);
$car->features->each(function ($item, $key) use($newCollection) {
$current = collect([
$item->group->name => $item->name,
]);
$newCollection->mergeRecursive($current);
});
dd($newCollection);
return view('pages.car', [
'car' => $car,
'features' => $newCollection,
]);
The only data in my collection is the dummy data I instantiated it with. What am I missing?
Any pointers? Thanks!
Ok I got the solution. No need to loop over the collection to create a new collection. There is a collection method groupBy() that does the trick:
$myCollection->groupBy('group_id'); returns this:
{
"1":[
{
"id":1,
"group_id":1,
"name":"metallic",
"pivot":{
"car_id":1,
"feature_id":1
},
"group":{
"id":1,
"name":"paint"
}
},
{
"id":2,
"group_id":1,
"name":"blue",
"pivot":{
"car_id":1,
"feature_id":2
},
"group":{
"id":1,
"name":"paint"
}
}
],
"2":[
{
"id":5,
"group_id":2,
"name":"gps",
"pivot":{
"car_id":1,
"feature_id":5
},
"group":{
"id":2,
"name":"interior"
}
},
{
"id":6,
"group_id":2,
"name":"leather seats",
"pivot":{
"car_id":1,
"feature_id":6
},
"group":{
"id":2,
"name":"interior"
}
}
]
}
Or I could use nested keys, $myCollection->groupBy('group.name'); returns this:
{
"paint":[
{
"id":1,
"group_id":1,
"name":"metallic",
"pivot":{
"car_id":1,
"feature_id":1
},
"group":{
"id":1,
"name":"paint"
}
},
{
"id":2,
"group_id":1,
"name":"blue",
"pivot":{
"car_id":1,
"feature_id":2
},
"group":{
"id":1,
"name":"paint"
}
}
],
"interior":[
{
"id":5,
"group_id":2,
"name":"gps",
"pivot":{
"car_id":1,
"feature_id":5
},
"group":{
"id":2,
"name":"interior"
}
},
{
"id":6,
"group_id":2,
"name":"leather seats",
"pivot":{
"car_id":1,
"feature_id":6
},
"group":{
"id":2,
"name":"interior"
}
}
]
}
Now it's possible to loop over the output using a #foreach() in blade layout/component:
#foreach ($features as $feature)
<h3>{{ $feature[0]->group->name }}</h3>
#foreach ($feature as $item)
<div>{{ $item->name }}</div>
#endforeach
#endforeach

How can I write a grok pattern for this log?

Log:
[2021-01-27T11:51:18,838][INFO ][logstash.setting.writabledirectory] Creating directory {:setting=>"path.dead_letter_queue", :path=>"C:\\Pippo\\logstash-7.6.1\\data\\dead_letter_queue"}
I advice you to use grokdebug online tool that will be very usefull for this kind of use case.
This is a first grok expression that match with a must part of your data line :
\[%{TIMESTAMP_ISO8601:timestamp}\]\[%{LOGLEVEL:level} \]\[%{GREEDYDATA:class}\] %{GREEDYDATA:action} \{:setting=>"%{GREEDYDATA:setting}", :path=>"%{PATH:path}"\}
This expression it's a starting point to your use case. The result of this grok expression is this :
{
"timestamp": [
[
"2021-01-27T11:51:18,838"
]
],
"YEAR": [
[
"2021"
]
],
"MONTHNUM": [
[
"01"
]
],
"MONTHDAY": [
[
"27"
]
],
"HOUR": [
[
"11",
null
]
],
"MINUTE": [
[
"51",
null
]
],
"SECOND": [
[
"18,838"
]
],
"ISO8601_TIMEZONE": [
[
null
]
],
"level": [
[
"INFO"
]
],
"class": [
[
"logstash.setting.writabledirectory"
]
],
"action": [
[
"Creating directory"
]
],
"setting": [
[
"path.dead_letter_queue"
]
],
"path": [
[
"C:\\Pippo\\logstash-7.6.1\\data\\dead_letter_queue"
]
],
"UNIXPATH": [
[
null
]
],
"WINPATH": [
[
"C:\\Pippo\\logstash-7.6.1\\data\\dead_letter_queue"
]
]
}

Index array of geopoints in Elasticsearch

Following is my (sample) Elastic search data which has a series of geo coordinates that I'm trying to index.
PUT geomap/_doc/1
{
"geometry": {
"coordinates": [
[
[
-10.8544921875,
49.82380908513249
],
[
-10.8544921875,
59.478568831926395
],
[
2.021484375,
59.478568831926395
],
[
2.021484375,
49.82380908513249
],
[
-10.8544921875,
49.82380908513249
]
]
]
}
}
and this is the elasticsearch mapping I've created for it.
PUT geomap
{
"mappings": {
"properties": {
"geometry": {
"properties": {
"coordinates": { "type": "geo_point" }
}
}
}
}
}
When I tried to insert the data it did not work. I suspect it is due to the fact that I've got arrays of array coordinates. When I updated the sample dataset to single array of coordinates it worked (below).
PUT geomap/_doc/1
{
"geometry": {
"coordinates": [
[
-10.8544921875,
49.82380908513249
],
[
-10.8544921875,
59.478568831926395
],
[
2.021484375,
59.478568831926395
],
[
2.021484375,
49.82380908513249
],
[
-10.8544921875,
49.82380908513249
]
]
}
}
I would be glad to know what mistake I've done in my mapping which doesn't let me to do so.
I suspect your doc is a polygon so you're gonna want to use geo_shape instead:
PUT geomap
{
"mappings": {
"properties": {
"geometry": {
"type": "geo_shape",
"strategy": "recursive"
}
}
}
}
Notice the "recursive" strategy too to support more spatial queries (at least in the newer ES releases).
PUT geomap/_doc/1
{
"geometry": {
"coordinates": [
[
[
-10.8544921875,
49.82380908513249
],
[
-10.8544921875,
59.478568831926395
],
[
2.021484375,
59.478568831926395
],
[
2.021484375,
49.82380908513249
],
[
-10.8544921875,
49.82380908513249
]
]
],
"type": "polygon"
}
}
Notice how the coords array was wrapped in one more array to comply w/ the geojson standard and that the type: polygon attribute was added.

GROK pattern for HTTP URL

Trying to parse below String
http://localhost:8080/client/session/login abc.xyz#yahoo.com backendorg with below GROK pattern
%{URIPATHPARAM:url}%{SPACE}%{EMAILADDRESS:email}%{SPACE}%{USERNAME:org} not getting complete url.
{
"org": "backendorg",
"url": "//localhost:8080/client/session/login",
"email": "abc.xyz#zinier.com"
}```
Anyone could suggest how to get complete URL.
GROK pattern:
%{URI:url}%{SPACE}(?<email>[a-zA-Z0-9_.+=:-]+#[0-9A-Za-z][0-9A-Za-z-]{0,62}(?:\.(?:[0-9A-Za-z][0-‌​9A-Za-z-]{0,62}))*)%{SPACE}%{USERNAME:org}
OUTPUT:
{
"url": [
[
"http://localhost:8080/client/session/login"
]
],
"URIPROTO": [
[
"http"
]
],
"USER": [
[
null
]
],
"USERNAME": [
[
null
]
],
"URIHOST": [
[
"localhost:8080"
]
],
"IPORHOST": [
[
"localhost"
]
],
"HOSTNAME": [
[
"localhost"
]
],
"IP": [
[
null
]
],
"IPV6": [
[
null
]
],
"IPV4": [
[
null
]
],
"port": [
[
"8080"
]
],
"URIPATHPARAM": [
[
"/client/session/login"
]
],
"URIPATH": [
[
"/client/session/login"
]
],
"URIPARAM": [
[
null
]
],
"SPACE": [
[
" ",
" "
]
],
"email": [
[
"abc.xyz#yahoo.com"
]
],
"org": [
[
"backendorg"
]
]
}
Use URI instead of URIPATHPARAM, like this: %{URI:url}%{SPACE}%{EMAILADDRESS:email}%{SPACE}%{USERNAME:org}

Resources