isc30 / blazor-analytics Goto Github PK
View Code? Open in Web Editor NEWBlazor extensions for Analytics: Google Analytics, GTAG, ...
License: MIT License
Blazor extensions for Analytics: Google Analytics, GTAG, ...
License: MIT License
Hi,
we need a switch to turn on/off Google analytics. The reason behind this is here in Germany we have GDPR that states that the end user must be able to switch off analytics tracking through consenting or rejecting cookies.
That means when the user doesn't consent to cookie policy the analytics mustn't be activated.
Could you implement this?
Thanks
Sven
Hi,
great package. I was wondering since Google now has the GA4 properties instead of the old Universal Google Analytics ID.
They call it the measurement ID now for GA4. Can i pass that ID now also in this package and does that work?
thanks
with the version from NuGet I still get the previous error listed at #6 regarding iurihelper vs NavigationManager.
Pulling the code from github and compiling myself I now get an issue with:
JavaScript interop calls cannot be issued at this time. This is because the component is being statically rendererd. When prerendering is enabled, JavaScript interop calls can only be performed during the OnAfterRenderAsync lifecycle method
Stack Trace
Microsoft.AspNetCore.Components.Server.Circuits.RemoteJSRuntime.BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
Microsoft.JSInterop.JSRuntime.InvokeAsync(string identifier, CancellationToken cancellationToken, object[] args)
Microsoft.JSInterop.JSRuntime.InvokeWithDefaultCancellation(string identifier, object[] args)
System.Threading.Tasks.ValueTask.get_Result()
System.Runtime.CompilerServices.ValueTaskAwaiter.GetResult()
Blazor.Analytics.GoogleAnalytics.Components.GoogleAnalyticsComponent.OnInitializedAsync() in GoogleAnalyticsComponent.cs
+
await JSRuntime.InvokeAsync(GoogleAnalyticsInterop.Configure,
Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)
Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToPendingTasks(Task task)
Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(ParameterView parameters)
Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewComponentFrame(ref DiffContext diffContext, int frameIndex)
Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewSubtree(ref DiffContext diffContext, int frameIndex)
Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InsertNewFrame(ref DiffContext diffContext, int newFrameIndex)
Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(ref DiffContext diffContext, int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl)
Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.ComputeDiff(Renderer renderer, RenderBatchBuilder batchBuilder, int componentId, ArrayRange oldTree, ArrayRange newTree)
Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment)
Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)
Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessPendingRender()
Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(int componentId, RenderFragment renderFragment)
Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()
Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync()
Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception)
Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToPendingTasks(Task task)
Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(ParameterView parameters)
Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderRootComponentAsync(int componentId, ParameterView initialParameters)
Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.CreateInitialRenderAsync(Type componentType, ParameterView initialParameters)
Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.RenderComponentAsync(Type componentType, ParameterView initialParameters)
Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext+<>c__11+<b__11_0>d.MoveNext()
Microsoft.AspNetCore.Mvc.ViewFeatures.StaticComponentRenderer.PrerenderComponentAsync(ParameterView parameters, HttpContext httpContext, Type componentType)
Microsoft.AspNetCore.Mvc.ViewFeatures.ComponentRenderer.PrerenderedServerComponentAsync(HttpContext context, ServerComponentInvocationSequence invocationId, Type type, ParameterView parametersCollection)
Microsoft.AspNetCore.Mvc.ViewFeatures.ComponentRenderer.RenderComponentAsync(ViewContext viewContext, Type componentType, RenderMode renderMode, object parameters)
Microsoft.AspNetCore.Mvc.TagHelpers.ComponentTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.g__Awaited|0_0(Task task, TagHelperExecutionContext executionContext, int i, int count)
EBSmith.Pages.Pages__Host.b__14_1() in _Host.cshtml
+
Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync()
EBSmith.Pages.Pages__Host.ExecuteAsync() in _Host.cshtml
+
Layout = null;
Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, bool invokeViewStarts)
Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable statusCode)
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable statusCode)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|21_0(ResourceInvoker invoker, IActionResult result)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|29_0<TFilter, TFilterAsync>(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext<TFilter, TFilterAsync>(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker)
Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Ivan,
thanks for putting this together. Really handy.
I've followed your instructions to include this in a Blazor client (WASM) project.
I think there is a problem with the component.
On page load I get an unhandled error in the UI and looking at the browser debugging console I see:
Unhandled exception rendering component: Cannot provide a value for property 'Analytics' on type 'Blazor.Analytics.Components.NavigationTracker'. There is no registered service of type 'Blazor.Analytics.IAnalytics'.
Any ideas?
Hi,
your app running in Azure sometimes gets the following exception:
fail: Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost[111]
Unhandled exception in circuit 'QgmPR1OxRgsVF5nW-MdwaOkwyjrAyXXzrdfrwQHBXy0'.
System.Threading.Tasks.TaskCanceledException: A task was canceled.
at Microsoft.JSInterop.JSRuntime.InvokeWithDefaultCancellation[T](String identifier, Object[] args)
at Blazor.Analytics.GoogleAnalytics.GoogleAnalyticsStrategy.TrackNavigation(String uri)
at Blazor.Analytics.Components.NavigationTracker.OnLocationChanged(String location)
at Blazor.Analytics.Components.NavigationTracker.OnLocationChanged(Object sender, LocationChangedEventArgs args)
at System.Threading.Tasks.Task.<>c.b__139_0(Object state)
at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.ExecuteSynchronously(TaskCompletionSource`1 completion, SendOrPostCallback d, Object state)
at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.<>c.<.cctor>b__23_0(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location where exception was thrown ---
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.ExecuteBackground(WorkItem item)
Currently, only GTag is supported but the architecture allows any kind of integrations.
Keeping this issue alive until we gather a list of the most used analytic APIs.
List of possible candidates:
I am currently using the library but I have a problem when entering the TrackingId since I do not have it when registering the service in the startup since I am also using the Finbuckle.MultiTenant library
I see that they have the service registration without the trackingId, how to use the service when the service only registers you in the following way?
services.AddGoogleAnalytics();
Hi, I'm successfully using your NuGet package but my website is now accepting two country domains and I need to conditionally configure the scoped service with a Google key depending on the domain of the http request. I guess I should first add the scoped service with its parameterless constructor and configure the service with the right key after building the WebApplication on Program.cs. May you publish in the Readme file an example of how to configure it if even possible?
Hi, I added this library to a small project I'm working on and it doesn't seem to do anything at all when targeting WebAssembly.
Project details:
Neither running on local or publishing on the web shows anything in Analytics. If I copy and paste the code provided by Google into the project's index.html, it shows data when run from both places.
I just wanted to ask if its possible to enable the tracking again after initially disabling it. I want to wait on a response from CookieBot if the statistics cookies have been accepted and before that they have to be disabled.
Thanks!
Hi,
is there possibility to add events?
Add PR checks for compilation
Hi,
how is possible to use the package from API in ASP.NET Core?
Is also possible to track a single user passing any ID?
Thank you.
This component is totally Compliance with GA4?
Best Regards,
the ContainerId seems not be existing...
The console.log
statements are very useful for debugging, but I'd like them to not be logged in production. Can we have a way to opt-in to logging but have it not logged by default?
The library works great btw, thank you!
This may be an issue separate from Blazor-Analytics (let me know if you agree). When trying to view the BlazorClient site in preview mode using GTM, the expected panel does not appear. Is this just an incompatibility with Blazor? I'm not sure if GTM needs to inject additional js to make this functionality work or if it is supposed to be included in the initial analytics scripts from the head element to insert the preview/debug iframe.
Just found out that someone made a javascript for google analytics which helps single page apps like blazor to correctly tracks urls you visit, external links you open and other features here https://github.com/googleanalytics/autotrack it works really well for me now
I have the following error on build after upgrading a working app to net 6
IOt is a hosted BSS/wasm app
C:\Program Files\dotnet\sdk\6.0.100-preview.7.21379.14\Sdks\Microsoft.NET.Sdk.Razor\build\netstandard2.0\Microsoft.NET.Sdk.Razor.StaticWebAssets.targets(676,7): error : Found conflicting definitions for the same asset 'Identity: C:\Users\mikes.SURCOUF\.nuget\packages\blazor-analytics\3.8.0\contentFiles\any\netstandard2.0\wwwroot\blazor-analytics.js, SourceType: Project, SourceId: MudBlazor.Docs.Client, ContentRoot: C:\Users\mikes.SURCOUF\source\repos\MudBlazor\src\MudBlazor.Docs.Client\wwwroot\, BasePath: wasm, RelativePath: blazor-analytics.js, AssetKind: All, AssetMode: All, AssetRole: Primary, RelatedAsset: , AssetTraitName: , AssetTraitValue: , CopyToOutputDirectory: Never, CopyToPublishDirectory: PreserveNewest, OriginalItemSpec: C:\Users\mikes.SURCOUF\.nuget\packages\blazor-analytics\3.8.0\contentFiles\any\netstandard2.0\wwwroot\blazor-analytics.js' and 'Identity: C:\Users\mikes.SURCOUF\.nuget\packages\blazor-analytics\3.8.0\contentFiles\any\netstandard2.0\wwwroot\blazor-analytics.js, SourceType: Discovered, SourceId: MudBlazor.Docs.Server, ContentRoot: C:\Users\mikes.SURCOUF\source\repos\MudBlazor\src\MudBlazor.Docs.Server\wwwroot\, BasePath: _content/MudBlazor.Docs.Server, RelativePath: blazor-analytics.js, AssetKind: All, AssetMode: All, AssetRole: Primary, RelatedAsset: , AssetTraitName: , AssetTraitValue: , CopyToOutputDirectory: Never, CopyToPublishDirectory: PreserveNewest,
OriginalItemSpec: C:\Users\mikes.SURCOUF\.nuget\packages\blazor-analytics\3.8.0\contentFiles\any\netstandard2.0\wwwroot\blazor-analytics.js' [C:\Users\mikes.SURCOUF\source\repos\MudBlazor\src\MudBlazor.Docs.Server\MudBlazor.Docs.Server.csproj]
The terminal process "C:\Program Files\PowerShell\7\pwsh.exe -Command dotnet build src/MudBlazor.sln /property:GenerateFullPaths=true /consoleloggerparameters:NoSummary" terminated with exit code: 1.
The namespace is Blazorx not Blazor. This needs to be changed in all samples.
Hi,
Small ask but can would it be possible to get a disclosure added to the package? I am using this package in a Blazor WASM app and trying to get google to approve our ads on the site and one of the final hurdles are proper software disclosure on all .dll that the client downloads.
Currently this package is one that is flagged by Google Ads.
Thanks
Request URL: https://localhost:44379/_content/Blazor-Analytics/blazor-analytics.js
Status Code: 404
I followed all the instructions for server side Blazor. How does the file get out of the nuget package and into the _content folder? I can see the file in wwwroot folder (not in any other folder, just at the root) in solution explorer but it's not actually there on the file system - it's full path is a path to the NuGet folder.
Any ideas of things I can investigate?
gtag('set', 'linker', {
'domains': ['domain1.de', 'domain2.de', 'domain3.de']
});
3.0.0.2 build from Nuget
Running server side blazor 3.1 (.net core 3.1.1)
Title says it all, getting a 404 for the blazor-analytics.js
Roll back to 3.0.0 and now blazor-analytics.js comes through fine, everything else seems fine so far.
Create a demo application
Hello, first of all, thank you for building this <3.
I'm using Xunit together with Bunit to write unit tests. (https://bunit.egilhansen.com/docs/providing-input/inject-services-into-components.html#injecting-services-in-c-based-tests)
To 'mock' an service injection they use a concrete class
ctx.Services.AddSingleton<IWeatherForecastService>(new WeatherForecastService());
I tried to
ctx.Services.AddSingleton<IAnalytics>(new ?);
Is there anything I could use for the IAnalytics. My tests now fail due the error
System.InvalidOperationException : Cannot provide a value for property 'Analytics' on type 'Pay1Euro.Client.Pages.Counter'. There is no registered service of type 'Blazor.Analytics.IAnalytics'.
My question is : In any project of yours, were you able to test this, and how did you do this?
My very basic Counter page that i'm trying to test.
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
[Inject]
private Blazor.Analytics.IAnalytics Analytics { get; set; }
private void IncrementCount()
{
currentCount++;
Analytics.TrackEvent("Increment", currentCount.ToString(), "CountPage");
}
}
Hi,
I am using Blazor Server Side app with .NET 6.
I tried to setup extension, but I got still 404 error. I know some while ago it was similar issue.
Maybe you know why it still appears?
GET https://www.mysite.com/_content/Blazor-Analytics/blazor-analytics.js net::ERR_ABORTED 404 (Not Found)
Preview 9 replaced IUriHelper
with NavigationManager
which throws a
System.TypeLoadException: "Could not load type 'Microsoft.AspNetCore.Components.IUriHelper' from assembly 'Microsoft.AspNetCore.Components, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'."
At startup because
Blazor.Analytics.GoogleAnalytics.GoogleAnalyticsComponent
is trying to load IUriHelper
Here:
When passing event value/category/label etc the parameters are passed incorrectly. In https://github.com/isc30/blazor-analytics/blob/master/src/Blazor.Analytics/GoogleAnalytics/GoogleAnalyticsStrategy.cs#L63 you call the interop with
await _jsRuntime.InvokeAsync<string>(
GoogleAnalyticsInterop.TrackEvent,
_trackingId, eventName, eventValue, eventCategory);
But in https://github.com/isc30/blazor-analytics/blob/master/src/Blazor.Analytics/GoogleAnalytics/Resources/GoogleAnalyticsInterop.ts#L46 the parameters are in a different order.
export function trackEvent(event: string, eventCategory: string, eventLabel: string, eventValue: string)
{
gtag("event", event, { event_category: eventCategory, event_label: eventLabel, value: eventValue });
if(this.debug){
console.log(`[GTAG][Event trigered]: ${event}`);
}
}
Looking at the history it seems there were two functions for trackEvent in GoogleAnalyticsInterop.ts
but one was removed in 36b752f#diff-6cdb5d4488ebfb7b2a0f7d2e59fea40eea0d17ed2bf53c4f5186d16950b06d9e. The removed one matched the arguments being passed in correctly.
Not sure how you want to resolve this but should be quite straightforward. Let me know if you'd like a pull request for it.
I don't have a tracking id during development or when deploying for self-hosting in a container.
When I don't provide a tracking id, I get a run-time error on the console and from the Blazor dev UI that the "tracking code is invalid".
I get a different error when I don't do a AddGoogleAnalytics
at all.
How is this to be handled?
Documentation on the readme file is inaccurate. I cannot pass in an event object. Why was this taken out?
I followed the setup info you provided.
I added NavigationTracker to the App.Razor
Sorry, there's nothing at this address.
and I added the JS to index.html
<script src="_content/Blazor-Analytics/blazor-analytics.js"></script>I added the service to Program.cs of the client
builder.Services.AddGoogleAnalytics("XXXXXXXXX");
But I am still not getting individual page view info in Google Analytics. It just registers the application was loaded/accessed.
I also experimented with
await Analytics.TrackEvent("PageView", null, "PageName");
in a couple of pages to see if calling the specific event would help but no joy.
Any suggestions or recommendations ?
Is it possible to track an event like this for example:
https://developers.google.com/gtagjs/reference/ga4-events#select_content
Why is "eventValue" an integer for TrackEvent (https://github.com/isc30/blazor-analytics/blob/master/src/Blazor.Analytics/GoogleAnalytics/GoogleAnalyticsStrategy.cs)?
I'm happy to do a PR if changes are needed to make it work.
Is there any way to disable tracking certain pages? It would be nice to have some way to disable it when not needed.
In v3.1.1 the tsconfig.json file seems to have been accidentally included in the Nuget package. This causes Visual Studio to generate errors when it can't find anything to compile. Deleting tsconfig.json from the nuget package cache suffices as a workaround.
If AdBlock Plus is turned on with the 'Block Additional Tracking' option turned on (which blocks blazor-analytics.js), it causes the circuit to crash so the website is no longer responsive.
blazor.server.js:1 [2022-10-08T19:16:12.354Z] Error: Microsoft.JSInterop.JSException: Could not find 'GoogleAnalyticsInterop.configure' ('GoogleAnalyticsInterop' was undefined).
Error: Could not find 'GoogleAnalyticsInterop.configure' ('GoogleAnalyticsInterop' was undefined).
at http://localhost:5015/_framework/blazor.server.js:1:497
at Array.forEach (<anonymous>)
at i.findFunction (http://localhost:5015/_framework/blazor.server.js:1:465)
at E (http://localhost:5015/_framework/blazor.server.js:1:2606)
at http://localhost:5015/_framework/blazor.server.js:1:3494
at new Promise (<anonymous>)
at kt.beginInvokeJSFromDotNet (http://localhost:5015/_framework/blazor.server.js:1:3475)
at http://localhost:5015/_framework/blazor.server.js:1:72061
at Array.forEach (<anonymous>)
at kt._invokeClientMethod (http://localhost:5015/_framework/blazor.server.js:1:72047)
at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, Object[] args)
at Blazor.Analytics.GoogleAnalytics.GoogleAnalyticsStrategy.Initialize(String trackingId)
at Blazor.Analytics.GoogleAnalytics.GoogleAnalyticsStrategy.TrackNavigation(String uri)
at Blazor.Analytics.Components.NavigationTracker.OnLocationChanged(String location)
at Blazor.Analytics.Components.NavigationTracker.OnAfterRenderAsync(Boolean firstRender)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
It would be nice to pass custom additional parameters to Google Analytics, such as customer number.
Thanks
like explained here:
https://developers.google.com/analytics/devguides/collection/gtagjs/cross-domain
gtag('config', 'GA_MEASUREMENT_ID', {
'linker': {
'accept_incoming': true
}
});
When I'm monitoring the "real time" of Google Analytics, it looks like the NavLink transitions are not being tracked.
Only when I refresh the page in the browser, the page views are tracked.
Is this normal?
Also, is there a solution?
Hey there many thanks for this awesome library. I would recommend trying to load scripts using Js Isolation way. This way consumers don't need to add scripts in index.html
. What's your thoughts on this?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.