Using PowerBI with Neo4j
There’s an excellent post by Cédric Charlier over at his blog about hooking Neo4j into PowerBI. It’s simple to follow and get’s you up and running, but I (as a PowerBI newbie) had a couple of spots where I ran into trouble – generally with assumptions I think that are made assuming that you know how to navigate around the PowerBI interface. (I didn’t).
So, here is a simple tutorial to get us non-BI people up and running!
I’ve written a Data Connector for Neo4j now – and I would heartily recommend you have a look at the new post I’ve written about it here: http://xclave.co.uk/2019/02/06/actually-using-the-new-dataconnector-for-powerbi/
The Setup Steps
First – we’ve got to install PowerBI – now, I didn’t sign up for an account, but downloaded it from the PowerBI website, and installing was simple and quick.
We also need to have Neo4j running, and you can use Community or Enterprise, it matters not – and we’ll want to put the ‘Movies’ dataset in there, so run your instance, and execute:
:play Movies
Now we’re ready to ‘BI’!
Step 1 – Start Power BI Desktop
This is pretty obvious, but in case you need it – click on the ‘Power BI Desktop’ link in your start menu – or double click on it if you went and put it on the Desktop. Crazy days.
Step 2 – Click on ‘Get Data’
That way we can get data!
Step 3 – Select ‘Blank Query’
Why not ‘web’ you ask? Well as we’re going to do some copy/pasting – it’s easier from a blank query point of view.
Step 4 – Advanced
In the query editor window that pops up, select ‘Advanced Editor’
Step 5 – Get Data!
We’re going to use the same query as Cédric as you can then use this post to augment his, so in the query editor simply paste:
let Source = Web.Contents( "http://localhost:7474/db/data/transaction/commit", [ Content=Text.ToBinary("{ ""statements"" : [ { ""statement"" : ""MATCH (tom:Person {name:'Tom Hanks'})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors), (coActors)-[:ACTED_IN]->(m2)<-[:ACTED_IN]-(cocoActors) WHERE NOT (tom)-[:ACTED_IN]->(m2) RETURN cocoActors.name AS Recommended, count(*) AS Strength ORDER BY Strength DESC""} ] }")] ) in Source
Oh noes! The same error as Cédric got – authentication. You can’t send the login details via changing the URL to be something like:
as that also fails, but you can send in the auth as a header, by adding this line:
Headers = [#"Authorization" = "Basic bmVvNGo6bmVv"],
What is this bmVvNGo6bmVv? Well, that’s the base64 encoded user/pass combo – which is a bit uh oh as you have to generate this 🙁
I’ve got two options here – LinqPad and Powershell
LinqPad
Using this bit of C# – obviously – you can write your own C# app in VS or whatever, but typically I use LinqPad for quick scripts.
var username = "neo4j"; var password = "neo"; var encoded = Encoding.ASCII.GetBytes(string.Format("{0}:{1}", username, password)); var base64 = Convert.ToBase64String(encoded); base64.Dump();
Powershell
This does pretty much the same, but can obviously be run in a Powershell prompt – which is nice!
Param(
[string]
$username,
[string]
$password ) $encoder = [system.Text.Encoding]::UTF8 $token = $username + “:” + $password $encoded = $encoder.GetBytes($token) $base64 = [System.Convert]::ToBase64String($encoded) Write-Output $base64
which is then used like:
GetAuthCode.ps1 –username neo4j –password neo
So, with this information, our new ‘Get data’ bit looks like:
let Source = Web.Contents( "http://localhost:7474/db/data/transaction/commit", [ Headers = [#"Authorization" = "Basic bmVvNGo6bmVv"], Content=Text.ToBinary("{ ""statements"" : [ { ""statement"" : ""MATCH (tom:Person {name:'Tom Hanks'})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors), (coActors)-[:ACTED_IN]->(m2)<-[:ACTED_IN]-(cocoActors) WHERE NOT (tom)-[:ACTED_IN]->(m2) RETURN cocoActors.name AS Recommended, count(*) AS Strength ORDER BY Strength DESC""} ] }")] ) in Source
which when we ‘preview’ gives us this:
Step 6 – Read as Json
Select the ‘localhost’ file and then choose ‘open as Json’ from the top menu:
You’ll notice once you’ve done this – your ‘Source’ has changed to now be ‘Json.Document(Web.Contents…)’
Step 7 – Navigation
First click on the ‘List’ of ‘Results.
This will take you to a screen that looks like this:
Note, you now have another ‘Step’ in the right hand bar – by the way – if you ever ’lose’ the Settings side bar – click on ‘View’ at the top and select ‘Query Settings’ to bring it back.
Then click on the ‘Record’ link, and then the ‘List’ for data:
Worth noting here, we’re still in the ‘Navigation’ step
Now you should have a list of ‘Record’s –
Step 8 – Table-ify
Go ahead and press the ‘To Table’ button, and then just ‘OK’ on the dialog that pops up:
Step 9 – Expand the Column
Records aren’t useful to Power BI (apparently) so – we need to expand that column out and to do that we click on the ‘Expand’ button – and in our case – we only want the ‘row’, not the meta, so unselect the ‘meta’ and press OK
Now you should see a row of ‘List’ and an extra step in our ‘Applied Steps’ list:
Step 10 – Add a custom column
So now we need to get the information out of these new ‘Lists’ – and to do that we need a custom column, so click on the ‘Custom Column’ button in the ‘Add Column’ tab:
In the dialog that pops up we want to have it say:
= Record.FromList([Column1.row], type[Name = text, Rank = number])
Then press OK, and you’ll have another Column called ‘Custom’, and another item in our Applied Steps:
Step 11 – Expand Custom
More records eh? Let’s expand it out, so as before, click on the ‘Expand’ button:
and in this case, we want all the columns:
Now you should have two new columns, and another step added:
Data! Yay!
Step 12 – Remove that non-useful row
Right click on the ‘Column1.row’ column and select Remove
Step 13 – Close & Apply
Now we have data in a format we can use in Power BI, let’s close and apply that query.
Step 14 – Use that data
Now – I’m no Power BI user – so this is super simple and pointless, but should get you going for experimenting.
After applying that query we’re back in the main desktop view, but now in the right hand side – we have some fields with our Query there:
Let’s VISUALIZE
I’m going to pick a ‘Treemap’ – because.
Empty treemap – Check!
Let’s set some data, I want to group by ‘Rank’, so I drag ‘Custom.Rank’ to the ‘Group’ section which is in the ‘Visualizations’ bar:
And then for ‘Values’ I’m going to drag the ‘Custom.Name’ field
Oooooh – colours:
Let’s expand our visualization by pressing the ‘Focus Mode’ button:
Boom! Full size
Now, if I hover over one of those boxes I get the brief info displayed:
Ace, only 2 names with a rank of 5, and to see who they are, right click and select ‘See Records’
And here they are:
No More Steps
If you want to just copy/paste the code, you can! Create a new blank query and open up the advanced editor and just paste the code below in. (NB There are probably loads of things which are rubbish about this implementation, lemme know!)
let Source = Json.Document( Web.Contents("http://localhost:7474/db/data/transaction/commit", [ Headers=[Authorization="Basic bmVvNGo6bmVv"], Content=Text.ToBinary("{""statements"" : [ { ""statement"" : ""MATCH (tom:Person {name:'Tom Hanks'})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors), (coActors)-[:ACTED_IN]->(m2)<-[:ACTED_IN]-(cocoActors) WHERE NOT (tom)-[:ACTED_IN]->(m2) RETURN cocoActors.name AS Recommended, count(*) AS Strength ORDER BY Strength DESC""} ] }") ])), results = Source[results], results1 = results{0}, data = results1[data], #"Converted to Table" = Table.FromList(data, Splitter.SplitByNothing(), null, null, ExtraValues.Error), #"Expanded Column1" = Table.ExpandRecordColumn(#"Converted to Table", "Column1", {"row"}, {"Column1.row"}), #"Added Custom" = Table.AddColumn(#"Expanded Column1", "Custom", each Record.FromList([Column1.row], type[Name = text, Rank = number])), #"Expanded Custom" = Table.ExpandRecordColumn(#"Added Custom", "Custom", {"Name", "Rank"}, {"Custom.Name", "Custom.Rank"}), #"Removed Columns" = Table.RemoveColumns(#"Expanded Custom",{"Column1.row"}) in #"Removed Columns"
Awesome solution, I took inspiration and created this one which uses the columns returned by neo4j to dynamically create the columns:
let
Source = (CypherQuery as text, neo4jHost as text, neo4jAuth as text, neo4jPort as number) => let
Source =
Json.Document(
Web.Contents(neo4jHost & “:” & Number.ToText(neo4jPort) & “/db/data/transaction/commit”,
[
Headers=[Authorization=”Basic ” & neo4jAuth],
Content=Text.ToBinary(“{“”statements”” : [ {
“”statement”” : “”” & CypherQuery & “””} ]
}”)
])),
results = Source[results],
results1 = results{0},
data = results1[data],
columns = results1[columns],
#”Converted to Table” = Table.FromList(data, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
#”Expanded Column1″ = Table.ExpandRecordColumn(#”Converted to Table”, “Column1”, {“row”}, {“Column1.row”}),
#”Column1 row” = #”Expanded Column1″[Column1.row],
ExpandAllQuandl = Table.FromRows(#”Column1 row”, columns)
in
ExpandAllQuandl
in
Source
[…] to the Power BI development experience. The good news is that we can just copy / paste from the old post I did and we can have a working function – taking into account that (a) we have the same data […]