Skip to content

Commit

Permalink
Merge pull request #51 from LayTec-AG/refactor/remove-newtonsoft
Browse files Browse the repository at this point in the history
Multiple changes
  • Loading branch information
sean-mcl committed Oct 26, 2020
2 parents dddb39a + 03571ee commit 0f64fd6
Show file tree
Hide file tree
Showing 97 changed files with 3,414 additions and 138 deletions.
12 changes: 11 additions & 1 deletion Plotly.Blazor.Examples/Components/ScatterChart.razor
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<button type="button" class="btn btn-primary" @onclick="() => chart.Clear()">Clear</button>
<button type="button" class="btn btn-primary" @onclick="() => DeleteScatter()">Pop</button>
<button type="button" class="btn btn-primary" @onclick="() => AddScatter()">Push</button>
<button type="button" class="btn btn-primary" @onclick="() => Restyle()">Rename First</button>
<button type="button" class="btn btn-primary" @onclick="() => PrependData()">Prepend First</button>
<button type="button" class="btn btn-primary" @onclick="() => ExtendData()">Extend First</button>
</div>
Expand Down Expand Up @@ -97,6 +98,15 @@
}
}

private async Task Restyle()
{
var updateScatterChart = new Scatter
{
Name = "Restyled Name"
};
await chart.Restyle(updateScatterChart, 0);
}

private async Task DeleteScatter()
{
await chart.DeleteTrace(0);
Expand All @@ -107,7 +117,7 @@
var (x, y) = Helper.GenerateData(0,100);
await chart.AddTrace(new Scatter
{

Name = $"ScatterTrace{data.Count+1}",
Mode = ModeFlag.Lines | ModeFlag.Markers,
X = x,
Expand Down
7 changes: 6 additions & 1 deletion Plotly.Blazor.Generator/CustomDic.txt
Original file line number Diff line number Diff line change
Expand Up @@ -245,4 +245,9 @@ xref=xRef
topojsonURL=topoJsonUrl
plotlyServerURL=plotlyServerUrl
ticklen=tickLen
ticklabelmode=tickLabelMode
ticklabelmode=tickLabelMode
xperiodalignment=xPeriodAlignment
yperiodalignment=yPeriodAlignment
xperiod0=xPeriod0
yperiod0=yPeriod0
rgba256=rgba256
46 changes: 44 additions & 2 deletions Plotly.Blazor.Generator/src/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
#pragma warning disable 1591

Expand All @@ -13,7 +13,7 @@ public static class Extensions
/// <summary>
/// Prepares an object for js interop operations, converting the object to a dictionary.
/// This operation can be customized using own serializer options.
/// Current it's not possible to define serializer options for the JSRuntime directly.
/// Currently it's not possible to define serializer options for the JSRuntime directly.
/// </summary>
/// <typeparam name="T">Type of the object.</typeparam>
/// <param name="obj">The object.</param>
Expand Down Expand Up @@ -110,6 +110,48 @@ public static void AddRange<T>(this IList<T> list, IEnumerable<T> items)
}
}

/// <summary>
/// Updates the properties of a given object, using a json string.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <param name="jsonString"></param>
/// <returns></returns>
internal static T Populate<T>(this T obj, string jsonString) where T : class
{
var newObj = JsonSerializer.Deserialize(jsonString, obj.GetType());

foreach (var property in newObj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy).Where(p => p.CanWrite))
{
if (!obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy).Where(p => p.CanWrite).Any(x => x.Name == property.Name && property.GetValue(newObj) != default))
{
continue;
}

if (property.GetType().IsClass && property.PropertyType.Assembly.FullName == typeof(T).Assembly.FullName)
{
var mapMethod = typeof(Extensions).GetMethod("Populate");
if(mapMethod == null) throw new NullReferenceException(nameof(mapMethod));
var genericMethod = mapMethod.MakeGenericMethod(property.GetValue(newObj).GetType());
var obj2 = genericMethod.Invoke(null, new[] { property.GetValue(newObj), JsonSerializer.Serialize(property.GetValue(newObj).ToString()) });

foreach (var property2 in obj2.GetType().GetProperties())
{
if (property2.GetValue(obj2) != null)
{
property?.GetValue(obj)?.GetType()?.GetProperty(property2.Name)?.SetValue(property.GetValue(obj), property2.GetValue(obj2));
}
}
}
else
{
property.SetValue(obj, property.GetValue(newObj));
}
}

return obj;
}

/// <summary>
/// Inserts data to an IList by given index.
/// </summary>
Expand Down
73 changes: 69 additions & 4 deletions Plotly.Blazor.Generator/src/PlotlyChart.razor
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@inject IJSRuntime JsRuntime
@using Microsoft.JSInterop
@using System.Text.Json;
@using System.Collections
@implements IDisposable

Expand Down Expand Up @@ -133,6 +134,70 @@
await JsRuntime.React(objectReference);
}

/// <summary>
/// Updates the chart layout using the current properties.
/// </summary>
/// <returns>Task</returns>
public async Task Relayout()
{
await JsRuntime.Relayout(objectReference);
}

/// <summary>
/// Updates all the traces using the provided parameter
/// </summary>
/// <param name="trace">New trace parameter, create new object and assign only update value</param>
/// <returns>Task</returns>
public async Task Restyle(ITrace trace)
{
await Restyle(trace, Data?.Select((v, index) => index).ToArray());
}

/// <summary>
/// Updates the specified trace using the provided parameter
/// </summary>
/// <param name="trace">New trace parameter, create new object and assign only update value</param>
/// <param name="index">Index of the trace. Use -1 e.g. to get the last one.</param>
/// <returns>Task</returns>
public async Task Restyle(ITrace trace, int index)
{
await Restyle(trace, new[] { index });
}

/// <summary>
/// Updates the specified traces using the provided parameter
/// </summary>
/// <param name="trace">New trace parameter, create new object and assign only update value</param>
/// <param name="indizes">Indizes of the traces. Use -1 e.g. to get the last one.</param>
/// <returns>Task</returns>
public async Task Restyle(ITrace trace, IEnumerable<int> indizes)
{
if (indizes == null || !indizes.Any())
{
throw new ArgumentException("You must specifiy at least one index.");
}
if (trace == null)
{
throw new ArgumentNullException(nameof(trace));
}
var absoluteIndizes = new List<int>();
foreach (var index in indizes)
{
var absoluteIndex = index < 0 ? Data.Count + index : index;
if (index > Data.Count - 1)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
absoluteIndizes.Add(absoluteIndex);
}
var json = JsonSerializer.Serialize(trace.PrepareJsInterop(PlotlyJsInterop.SerializerOptions));
foreach (var currentTrace in absoluteIndizes.Select(index => Data[index]))
{
currentTrace.Populate(json); // apply change to the existing object
}
await JsRuntime.Restyle(objectReference, trace, absoluteIndizes.ToArray());
}

/// <summary>
/// Same as <see cref="React"/>.
/// </summary>
Expand Down Expand Up @@ -305,15 +370,15 @@
}

// Add the data to the current traces
foreach (var index in indizesArr)
foreach (var index in indizesArr.Select((Value, Index) => new { Value, Index }))
{
var currentTrace = index < 0 ? Data[^index] : Data[index];
var currentTrace = index.Value < 0 ? Data[Data.Count + index.Value] : Data[index.Value];
var traceType = currentTrace.GetType();

var xArray = x as IEnumerable<object>[] ?? x.ToArray();
if (xArray.Length > 0)
{
var currentXData = index < 0 ? xArray.ElementAt(xArray.Length - 1) : xArray.ElementAt(index);
var currentXData = xArray.ElementAt(index.Index);
var xData = currentXData as object[] ?? currentXData.ToArray();
if (xData.Length > 0)
{
Expand All @@ -324,7 +389,7 @@
var yArray = y as IEnumerable<object>[] ?? y.ToArray();
if (yArray.Length > 0)
{
var currentYData = index < 0 ? yArray.ElementAt(yArray.Length + index) : yArray.ElementAt(index);
var currentYData = yArray.ElementAt(index.Index);
var yData = currentYData as object[] ?? currentYData.ToArray();
if (yData.Length > 0)
{
Expand Down
109 changes: 108 additions & 1 deletion Plotly.Blazor.Generator/src/PlotlyConverter.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

#pragma warning disable 1591

namespace Plotly.Blazor
Expand Down Expand Up @@ -32,7 +35,111 @@ public class PlotlyConverter<T> : JsonConverter<T>
{
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException("Expected startObject.");
}

var allProperties = typeToConvert.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy).Where(p => p.CanWrite).ToList();

var arrayProperties = allProperties.Where(p => p.GetCustomAttribute<ArrayAttribute>() != null).ToArray();
var subplotProperties = allProperties.Where(p => p.GetCustomAttribute<SubplotAttribute>() != null && p.PropertyType.GetGenericTypeDefinition() == typeof(IList<>)).ToArray();
var otherProperties = allProperties.Where(p => p.GetCustomAttribute<SubplotAttribute>() == null && p.GetCustomAttribute<ArrayAttribute>() == null).ToArray();

var result = (T)Activator.CreateInstance(typeToConvert);

while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return result;
}

if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException("Expected propertyName.");
}

var propertyName = reader.GetString();

var arrayProperty = arrayProperties.FirstOrDefault(p =>
string.Equals(p.Name, $"{propertyName}Array", StringComparison.OrdinalIgnoreCase));

var subplotProperty = subplotProperties.FirstOrDefault(p =>
Regex.IsMatch(propertyName, $"{p.Name}\\d*", RegexOptions.IgnoreCase));

var otherProperty = otherProperties.FirstOrDefault(p =>
string.Equals(p.Name, propertyName, StringComparison.OrdinalIgnoreCase));


// Determine if its an "array" property which could be either a single object or an array
if (arrayProperty != null)
{
// If its an array property, check if it start with an array token type
if (reader.TokenType == JsonTokenType.StartArray)
{
var type = arrayProperty.PropertyType;
arrayProperty.SetValue(result, JsonSerializer.Deserialize(ref reader, type, options));
}
// Otherwise set the standalone value instead of the array property
else
{

var standalonePropertyName = Regex.Match(arrayProperty.Name, "(.*?)Array").Groups[1].Value;
var standaloneProperty =
otherProperties.FirstOrDefault(p => string.Equals(standalonePropertyName, p.Name, StringComparison.OrdinalIgnoreCase));
if (standaloneProperty == null)
throw new JsonException("The array property is missing its single counterpart.");

var type = standaloneProperty.PropertyType;
standaloneProperty.SetValue(result, JsonSerializer.Deserialize(ref reader, type, options));
}

}
else if (subplotProperty != null)
{
// Get the current index
var match = Regex.Match(propertyName, $"{subplotProperty.Name}(\\d*)", RegexOptions.IgnoreCase);
int index;
index = int.TryParse(match.Groups[1].Value, out index) ? index - 1 : 0;

// Create a list, if a list does not exists yet
if (subplotProperty.GetValue(result) == null)
{
var constructedListType = typeof(List<>).MakeGenericType(subplotProperty.PropertyType.GenericTypeArguments[0]);
subplotProperty.SetValue(result, Activator.CreateInstance(constructedListType));
}

var list = subplotProperty.GetValue(result);
var underlyingType = subplotProperty.PropertyType.GetGenericArguments()[0];

// Fill the list with nulls, if not enough elements are present
while (((IList)list)?.Count < index)
{
list.GetType().GetMethod("Add")?.Invoke(list, new object[]{null});
}

list?.GetType().GetMethod("Insert")?.Invoke(list,
new[]
{
index,
JsonSerializer.Deserialize(ref reader, underlyingType, options)
});

}
else if (otherProperty != null)
{
var type = otherProperty.PropertyType;
otherProperty.SetValue(result, JsonSerializer.Deserialize(ref reader, type, options));
}
else
{
// ignore unknown/read-only properties
reader.Skip();
}
}

throw new JsonException();
}

public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
Expand Down
32 changes: 31 additions & 1 deletion Plotly.Blazor.Generator/src/PlotlyJsInterop.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
Expand All @@ -16,7 +17,7 @@ public static class PlotlyJsInterop
// Should be fixed in future blazor wasm releases
private const string PlotlyInterop = "plotlyInterop";

private static readonly JsonSerializerOptions SerializerOptions = new JsonSerializerOptions
internal static readonly JsonSerializerOptions SerializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = null,
IgnoreNullValues = true,
Expand Down Expand Up @@ -124,5 +125,34 @@ public static async Task Purge(this IJSRuntime jsRuntime, DotNetObjectReference<
{
await jsRuntime.InvokeVoidAsync($"{PlotlyInterop}.purge", objectReference.Value.Id);
}

/// <summary>
/// An efficient means of updating the layout object of an existing plot.
/// </summary>
/// <param name="jsRuntime">The js runtime.</param>
/// <param name="objectReference">The object reference.</param>
public static async Task Relayout(this IJSRuntime jsRuntime, DotNetObjectReference<PlotlyChart> objectReference)
{
await jsRuntime.InvokeVoidAsync($"{PlotlyInterop}.relayout",
objectReference.Value.Id,
objectReference.Value.Layout?.PrepareJsInterop(SerializerOptions));
}

/// <summary>
/// Can be used to add data to an existing trace.
/// </summary>
/// <param name="jsRuntime">The js runtime.</param>
/// <param name="objectReference">The object reference.</param>
/// <param name="trace">The new trace parameter</param>
/// <param name="indizes">Indizes.</param>
public static async Task Restyle(this IJSRuntime jsRuntime,
DotNetObjectReference<PlotlyChart> objectReference, ITrace trace, IEnumerable<int> indizes)
{
await jsRuntime.InvokeVoidAsync($"{PlotlyInterop}.restyle", objectReference.Value.Id, trace?.PrepareJsInterop(SerializerOptions), indizes);
}




}
}
Loading

0 comments on commit 0f64fd6

Please sign in to comment.