mirror of
https://github.com/awatertrevi/xamarin-neo4j.git
synced 2026-09-22 09:05:29 +00:00
graph visualization
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:pancakeView="clr-namespace:Xamarin.Forms.PancakeView;assembly=Xamarin.Forms.PancakeView"
|
||||
xmlns:fonts="clr-namespace:Xamarin.Neo4j.Fonts;assembly=Xamarin.Neo4j"
|
||||
x:Class="Xamarin.Neo4j.Controls.QueryBuilder">
|
||||
<pancakeView:PancakeView BackgroundColor="White" CornerRadius="2" Padding="4" Margin="16">
|
||||
<pancakeView:PancakeView CornerRadius="2">
|
||||
<pancakeView:PancakeView.Border>
|
||||
<pancakeView:Border Thickness="2" Color="#d8e5f1" />
|
||||
</pancakeView:PancakeView.Border>
|
||||
|
||||
<StackLayout Orientation="Horizontal">
|
||||
<Editor x:Name="editor" HorizontalOptions="FillAndExpand" AutoSize="TextChanges" />
|
||||
|
||||
<Button Clicked="InvokeExecuteQuery" Text="{x:Static fonts:FontAwesomeSolid.Play}" FontFamily="{StaticResource FontAwesomeSolid}" VerticalOptions="CenterAndExpand" HorizontalOptions="End" FontSize="20" Margin="0, 0, 5, 0" />
|
||||
</StackLayout>
|
||||
</pancakeView:PancakeView>
|
||||
</pancakeView:PancakeView>
|
||||
</ContentView>
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
namespace Xamarin.Neo4j.Controls
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class QueryBuilder : ContentView
|
||||
{
|
||||
public event EventHandler ExecuteQuery;
|
||||
|
||||
public QueryBuilder()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
editor.SetBinding(Editor.TextProperty, new Binding(nameof(Query), source: this));
|
||||
}
|
||||
|
||||
#region Bindable Properties
|
||||
|
||||
public string Query
|
||||
{
|
||||
get => (string)GetValue(QueryProperty);
|
||||
set => SetValue(QueryProperty, value);
|
||||
}
|
||||
|
||||
public static BindableProperty QueryProperty = BindableProperty.Create("Query", typeof(string),
|
||||
typeof(QueryBuilder), string.Empty, BindingMode.TwoWay);
|
||||
|
||||
#endregion
|
||||
|
||||
private void InvokeExecuteQuery (object sender, EventArgs e)
|
||||
{
|
||||
ExecuteQuery?.Invoke(sender, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:fonts="clr-namespace:Xamarin.Neo4j.Fonts;assembly=Xamarin.Neo4j"
|
||||
x:Class="Xamarin.Neo4j.Controls.QueryResultCell">
|
||||
<StackLayout Orientation="Horizontal">
|
||||
<Label Text="{Binding Query}" />
|
||||
<Button Clicked="OpenNeovis" Text="{x:Static fonts:FontAwesomeSolid.ProjectDiagram}" FontFamily="{StaticResource FontAwesomeSolid}" VerticalOptions="CenterAndExpand" FontSize="20" Margin="0, 0, 5, 0" />
|
||||
</StackLayout>
|
||||
</ViewCell>
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
using Xamarin.Neo4j.Models;
|
||||
using Xamarin.Neo4j.Pages;
|
||||
|
||||
namespace Xamarin.Neo4j.Controls
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class QueryResultCell : ViewCell
|
||||
{
|
||||
public QueryResult QueryResult => (QueryResult) BindingContext;
|
||||
|
||||
public QueryResultCell()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private async void OpenNeovis(object sender, EventArgs e)
|
||||
{
|
||||
await App.Current.MainPage.Navigation.PushAsync(new VisualizationPage(QueryResult));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// QueryResult.cs
|
||||
//
|
||||
// Trevi Awater
|
||||
// 13-11-2021
|
||||
//
|
||||
// © Xamarin.Neo4j
|
||||
//
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Neo4j.Driver;
|
||||
|
||||
namespace Xamarin.Neo4j.Models
|
||||
{
|
||||
public class QueryResult
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
public string Query { get; set; }
|
||||
|
||||
public Neo4jConnectionString ConnectionString { get; set; }
|
||||
|
||||
public IEnumerable<INode> Nodes { get; set; }
|
||||
|
||||
public IEnumerable<IRelationship> Relationships { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:fonts="clr-namespace:Xamarin.Neo4j.Fonts;assembly=Xamarin.Neo4j"
|
||||
xmlns:controls="clr-namespace:Xamarin.Neo4j.Controls;assembly=Xamarin.Neo4j"
|
||||
x:Class="Xamarin.Neo4j.Pages.SessionPage">
|
||||
<NavigationPage.TitleView>
|
||||
<StackLayout Spacing="0">
|
||||
@@ -25,8 +26,15 @@
|
||||
|
||||
<ContentPage.Content>
|
||||
<StackLayout>
|
||||
<Editor Text="{Binding Query}" AutoSize="TextChanges" />
|
||||
<Button Text="Run Query" Command="{Binding Commands[RunQuery]}" />
|
||||
<controls:QueryBuilder ExecuteQuery="OnExecuteQuery" Query="{Binding Query}" />
|
||||
|
||||
<ListView ItemsSource="{Binding QueryResults}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<controls:QueryResultCell />
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</StackLayout>
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
||||
|
||||
@@ -14,6 +14,8 @@ namespace Xamarin.Neo4j.Pages
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class SessionPage : ContentPage
|
||||
{
|
||||
private SessionViewModel ViewModel => (SessionViewModel) BindingContext;
|
||||
|
||||
public SessionPage(Neo4jConnectionString connectionString)
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -25,5 +27,10 @@ namespace Xamarin.Neo4j.Pages
|
||||
{
|
||||
databasePicker.Focus();
|
||||
}
|
||||
|
||||
private void OnExecuteQuery(object sender, EventArgs e)
|
||||
{
|
||||
ViewModel.ExecuteQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
Title="Graph"
|
||||
x:Class="Xamarin.Neo4j.Pages.VisualizationPage">
|
||||
<ContentPage.Content>
|
||||
<WebView Source="{Binding Source}" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
|
||||
</ContentPage.Content>
|
||||
</ContentPage>
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
using Xamarin.Neo4j.Models;
|
||||
using Xamarin.Neo4j.ViewModels;
|
||||
|
||||
namespace Xamarin.Neo4j.Pages
|
||||
{
|
||||
[XamlCompilation(XamlCompilationOptions.Compile)]
|
||||
public partial class VisualizationPage : ContentPage
|
||||
{
|
||||
public VisualizationPage(QueryResult queryResult)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
BindingContext = new VisualizationViewModel(Navigation, queryResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,14 +71,54 @@ namespace Xamarin.Neo4j.Services
|
||||
return databases;
|
||||
}
|
||||
|
||||
public async Task ExecuteQuery(string query, string database)
|
||||
public async Task<QueryResult> ExecuteQuery(string query, Neo4jConnectionString connectionString)
|
||||
{
|
||||
var session = GraphClient.Driver.AsyncSession(d => d.WithDatabase(database));
|
||||
try
|
||||
{
|
||||
var session = GraphClient.Driver.AsyncSession(d => d.WithDatabase(connectionString.Database));
|
||||
var cursor = await session.RunAsync(query);
|
||||
|
||||
var nodes = new List<INode>();
|
||||
var relationships = new List<IRelationship>();
|
||||
|
||||
while (await cursor.FetchAsync())
|
||||
{
|
||||
foreach (var record in cursor.Current.Values)
|
||||
{
|
||||
var value = record.Value;
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case INode _:
|
||||
nodes.Add(value.As<INode>());
|
||||
break;
|
||||
|
||||
case IRelationship _:
|
||||
relationships.Add(value.As<IRelationship>());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new QueryResult()
|
||||
{
|
||||
Success = true,
|
||||
Query = query,
|
||||
ConnectionString = connectionString,
|
||||
Nodes = nodes,
|
||||
Relationships = relationships
|
||||
};
|
||||
}
|
||||
|
||||
catch (ClientException e)
|
||||
{
|
||||
return new QueryResult()
|
||||
{
|
||||
Success = false,
|
||||
Query = query,
|
||||
ConnectionString = connectionString,
|
||||
ErrorMessage = e.Message
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,17 @@
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Acr.UserDialogs;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Neo4j.Annotations;
|
||||
using Xamarin.Neo4j.Models;
|
||||
using Xamarin.Neo4j.Pages;
|
||||
using Xamarin.Neo4j.Services;
|
||||
|
||||
namespace Xamarin.Neo4j.ViewModels
|
||||
@@ -29,20 +33,36 @@ namespace Xamarin.Neo4j.ViewModels
|
||||
|
||||
private List<Database> _availableDatabases;
|
||||
|
||||
private ObservableCollection<QueryResult> _queryResults;
|
||||
|
||||
private Neo4jConnectionString _connectionString;
|
||||
|
||||
private string _query;
|
||||
|
||||
public SessionViewModel(INavigation navigation, Neo4jConnectionString connectionString) : base(navigation)
|
||||
{
|
||||
_connectionString = connectionString;
|
||||
|
||||
_neo4jService = DependencyService.Resolve<Neo4jService>();
|
||||
|
||||
Commands.Add("RunQuery", new Command(async () =>
|
||||
{
|
||||
_neo4jService.ExecuteQuery(Query, CurrentDatabase.Name);
|
||||
}));
|
||||
QueryResults = new ObservableCollection<QueryResult>();
|
||||
|
||||
InitializeConnection(connectionString);
|
||||
}
|
||||
|
||||
public async void ExecuteQuery()
|
||||
{
|
||||
_connectionString.Database = CurrentDatabase.Name;
|
||||
|
||||
var result = await _neo4jService.ExecuteQuery(Query, _connectionString);
|
||||
|
||||
if (result.Success)
|
||||
QueryResults.Add(result);
|
||||
|
||||
else
|
||||
UserDialogs.Instance.Alert(result.ErrorMessage);
|
||||
}
|
||||
|
||||
private async void InitializeConnection(Neo4jConnectionString connectionString)
|
||||
{
|
||||
await _neo4jService.EstablishConnection(connectionString);
|
||||
@@ -88,6 +108,18 @@ namespace Xamarin.Neo4j.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableCollection<QueryResult> QueryResults
|
||||
{
|
||||
get => _queryResults;
|
||||
|
||||
set
|
||||
{
|
||||
_queryResults = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public string Query
|
||||
{
|
||||
get => _query;
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// VisualizationViewModel.cs
|
||||
//
|
||||
// Trevi Awater
|
||||
// 10-01-2022
|
||||
//
|
||||
// © Xamarin.Neo4j
|
||||
//
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Neo4j.Annotations;
|
||||
using Xamarin.Neo4j.Models;
|
||||
|
||||
namespace Xamarin.Neo4j.ViewModels
|
||||
{
|
||||
public class VisualizationViewModel : ViewModelBase, INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
private HtmlWebViewSource _source;
|
||||
|
||||
public VisualizationViewModel(INavigation navigation, QueryResult queryResult) : base(navigation)
|
||||
{
|
||||
ParseNeovisHtml(queryResult);
|
||||
}
|
||||
|
||||
private void ParseNeovisHtml(QueryResult queryResult)
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var resourceName = "Xamarin.Neo4j.Visualization.neovis.html";
|
||||
|
||||
using (var stream = assembly.GetManifestResourceStream(resourceName))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
var result = reader.ReadToEnd();
|
||||
|
||||
result = result.Replace("{{host}}", queryResult.ConnectionString.Host);
|
||||
result = result.Replace("{{port}}", queryResult.ConnectionString.Port.ToString());
|
||||
result = result.Replace("{{database}}", queryResult.ConnectionString.Database);
|
||||
result = result.Replace("{{username}}", queryResult.ConnectionString.Username);
|
||||
result = result.Replace("{{password}}", queryResult.ConnectionString.Password);
|
||||
result = result.Replace("{{encryption}}", queryResult.ConnectionString.Encrypted ? "ENCRYPTION_ON" : "ENCRYPTION_OFF");
|
||||
result = result.Replace("{{query}}", queryResult.Query);
|
||||
|
||||
Source = new HtmlWebViewSource
|
||||
{
|
||||
Html = result
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[NotifyPropertyChangedInvocator]
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
#region Bindable Properties
|
||||
|
||||
public HtmlWebViewSource Source
|
||||
{
|
||||
get => _source;
|
||||
|
||||
set
|
||||
{
|
||||
_source = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Neovis.js Simple Example</title>
|
||||
<style type="text/css">
|
||||
html, body {
|
||||
font: 16pt arial;
|
||||
}
|
||||
|
||||
#viz {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
border: 1px solid lightgray;
|
||||
font: 22pt arial;
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdn.neo4jlabs.com/neovis.js/v1.5.0/neovis.js"></script>
|
||||
</head>
|
||||
<body onload="draw()">
|
||||
<div id="viz"></div>
|
||||
</body>
|
||||
|
||||
<script type="text/javascript">
|
||||
var viz;
|
||||
|
||||
function draw() {
|
||||
var config = {
|
||||
container_id: "viz",
|
||||
server_url: "bolt://{{host}}:{{port}}",
|
||||
server_user: "{{username}}",
|
||||
server_database: "{{database}}",
|
||||
server_password: "{{password}}",
|
||||
encrypted: "{{encryption}}",
|
||||
initial_cypher: "{{query}}"
|
||||
};
|
||||
|
||||
viz = new NeoVis.default(config);
|
||||
viz.render();
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
@@ -14,9 +14,15 @@
|
||||
<PackageReference Include="Neo4jClient" Version="4.1.18" />
|
||||
<PackageReference Include="Xamarin.Essentials" Version="1.7.0" />
|
||||
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2196" />
|
||||
<PackageReference Include="Xamarin.Forms.PancakeView" Version="2.3.0.759" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Converters" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Visualization\neovis.html" />
|
||||
<EmbeddedResource Include="Visualization\neovis.html" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user