Giter Club home page Giter Club logo

floggy's Introduction

Loggy

Loggy icon

Highly customizable logger for dart that uses mixins to show all the needed info.

Setup

Add logger package to your project:

dependencies:
    loggy: ^2.0.2

Usage

Now once you added loggy to your project you can start using it. First, you need to initialize it:

import 'package:loggy/loggy.dart';

main() {
  Loggy.initLoggy();
}

In case you just want to log something without adding mixin you can use GlobalLoggy that is accessible everywhere, and it will follow the rules set in initLoggy method

import 'package:loggy/loggy.dart';

class DoSomeWork {
 DoSomeWork() {
   logDebug('This is debug message');
   logInfo('This is info message');
   logWarning('This is warning message');
   logError('This is error message');
 }
}

While global loggy is easier to use, it cannot be easily filtered, and it cannot fetch the calling class.

By using mixins to access our logger, you can get more info from loggy, now I will show you how to use default types (UiLoggy, NetworkLoggy). Later in the customizing loggy part, I will show you how you can easily add more types depending on the specific use case.

import 'package:loggy/loggy.dart';

class DoSomeWork with UiLoggy {
 DoSomeWork() {
   loggy.debug('This is debug message');
   loggy.info('This is info message');
   loggy.warning('This is warning message');
   loggy.error('This is error message');
 }
}

As you can see with the magic of mixins you already know the class name from where the log has been called as well as which logger made the call. Now you can use loggy through the app.

[D] UI Loggy - DoSomeWork: This is debug message
[I] UI Loggy - DoSomeWork: This is info message
[W] UI Loggy - DoSomeWork: This is warning message
[E] UI Loggy - DoSomeWork: This is error message

Loggy can take anything as it's log message, even closures (they are evaluated only if the log has been shown)

loggy.info(() {
  /// You can do what you want here!
  const _s = 0 / 0;
  return List.generate(10, (_) => _s)
          .fold<String>('', (previousValue, element) => previousValue += element.toString()) +
      ' Batman';
});

Customization

Printer

Printer or how our log is displayed can be customized a lot, by default loggy will use DefaultPrinter, you can replace this by specifying different logPrinter on initialization, you can use PrettyPrinter that is already included in loggy. You can also easily make your printer by extending the LoggyPrinter class.

You can customize logger on init with the following:

import 'package:loggy/loggy.dart';

void main() {
 // Call this as soon as possible (Above runApp)
 Loggy.initLoggy(
  logPrinter: const PrettyPrinter(),
 );
}

Loggy with PrettyPrinter:

๐Ÿ› 12:22:49.712326 DEBUG    UI Loggy - DoSomeWork - This is debug message
๐Ÿ‘ป 12:22:49.712369 INFO     UI Loggy - DoSomeWork - This is info message
โš ๏ธ 12:22:49.712403 WARNING  UI Loggy - DoSomeWork - This is warning message
โ€ผ๏ธ 12:22:49.712458 ERROR    UI Loggy - DoSomeWork - This is error message

One useful thing is specifying different printer for release that logs to Crashlytics/Sentry instead of console.

You could create your own CrashlyticsPrinter by extending Printer and use it like:

  Loggy.initLoggy(
    logPrinter: (kReleaseMode) ? CrashlyticsPrinter() : PrettyPrinter(),
    ...
  );

Log options

By providing LogOptions you need to specify LogLevel that will make sure only levels above what is specified will be shown.

Here you can also control some logging options by changing the stackTraceLevel, by specifying level will extract stack trace before the log has been invoked, for all LogLevel severities above the specified one.

Setting stackTraceLevel to LogLevel.error:

๐Ÿ› 12:26:48.432602 DEBUG    UI Loggy - DoSomeWork - This is debug message
๐Ÿ‘ป 12:26:48.432642 INFO     UI Loggy - DoSomeWork - This is info message
โš ๏ธ 12:26:48.432676 WARNING  UI Loggy - DoSomeWork - This is warning message
โ€ผ๏ธ 12:26:48.432715 ERROR    UI Loggy - DoSomeWork - This is error message
#0      Loggy.log (package:loggy/src/loggy.dart:195:33)
#1      Loggy.error (package:loggy/src/loggy.dart:233:73)
#2      new DoSomeWork (.../loggy/example/loggy_example.dart:29:11)
#3      main (.../loggy/example/loggy_example.dart:21:3)
#4      _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:301:19)
#5      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)

Custom loggers

You can have as many custom loggers as you want, by default you are provided with 2 types: NetworkLoggy and UiLoggy

To make a custom logger you just need to make a new mixin that implements LoggyType and returns new logger with mixin type:

import 'package:loggy/loggy.dart';

mixin CustomLoggy implements LoggyType {
  @override
  Loggy<CustomLoggy> get loggy => Loggy<CustomLoggy>('Custom Loggy - $runtimeType');
}

Then to use it just add with CustomLoggy to the class where you want to use it.

Custom log levels

You can add new LogLevel to log like this:

// LogLevel is just a class with `name` and `priority`. Priority can go from 1 - 99 inclusive.
const LogLevel socketLevel = LogLevel('socket', 32);

When adding a new level it's also recommended extending the Loggy class as well to add quick function for that level.

extension SocketLoggy on Loggy {
  void socket(dynamic message, [Object error, StackTrace stackTrace]) => log(socketLevel, message, error, stackTrace);
}

You can now use new log level in the app:

loggy.socket('This is log with socket log level');

Filtering

Now you have a lot of different types and levels how to find what you need? You may need to filter some of them. We have WhitelistFilter, BlacklistFilter and CustomLevelFilter.

Filtering is a way to limit log output without actually changing or removing existing loggers. Whitelisting some logger types will make sure only logs from that specific type are shown. Blacklisting will do the exact opposite of that. This is useful if your loggers log ton of data and pollute the console so it's hard to see valuable information.

  Loggy.initLoggy(
    ... // other stuff
    filters: [
      BlacklistFilter([SocketLoggy]) // Don't log logs from SocketLoggy
    ],
  );

More loggers?

Do you need more loggers? No problem!

Any class using Loggy mixin can make new child loggers with newLoggy(name) or detachedLoggy(name).

Child logger

newLoggy(name) will create a new child logger that will be connected to the parent logger and share the same options. Child loggy will have parent name included as the prefix on a child's name, divided by ..

Detached logger

detachedLoggy(name) is a logger that has nothing to do with the parent loggy and all options will be ignored. If you want to see those logs you need to attach a printer to it.

final _logger = detachedLoggy('Detached logger', logPrinter: DefaultPrinter());
_logger.level = const LogOptions(LogLevel.all);
// Add printer

Loggy ๐Ÿ’™ Flutter

Extensions that you can use in Flutter to make our logs look nicer. In order to fully use Loggy features in flutter make sure you are importing flutter_loggy pub package. In it you can find printer for flutter console PrettyDeveloperPrinter and widget that can show you all the logs: LoggyStreamWidget

Pretty developer printer

Loggy.initLoggy(
  logPrinter: PrettyDeveloperPrinter(),
);

This printer uses dart:developer and can write error messages in red, and it gives us more flexibility. This way you can modify this log a bit more and remove log prefixes (ex. [ ] I/flutter (21157))

To see logs in-app you can use StreamPrinter and pass any other printer to it. Now you can use LoggyStreamWidget to show logs in a list.

Loggy ๐Ÿ’™ Dio as well!

Extension for loggy. Includes the interceptor and pretty printer to use with Dio. In order to use Dio with Loggy you will have to import flutter_loggy_dio package, that will include an interceptor and new loggy type for Dio calls.

Usage

For Dio you included special DioLoggy that can be filtered, and LoggyDioInterceptor that will connect to Dio and print out requests and responses.

Dio dio = Dio();
dio.interceptors.add(LoggyDioInterceptor());

That will use Loggy options and levels, you can change the default LogLevel for request, response, and error.

Setup

In IntelliJ/Studio you can collapse the request/response body: Gif showing collapsible body

Set up this is by going to Preferences -> Editor -> General -> Console and under Fold console lines that contain add these 4 rules: โ•‘, โ•Ÿ, โ•” and โ•š. Settings

Features and bugs

Please file feature requests and bugs at the issue tracker.

floggy's People

Contributors

debkanchan avatar franmaric avatar gmcdowell avatar itsjokr avatar khaleelsh avatar lukaknezic avatar zenled avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

floggy's Issues

Why does GlobalLoggy only allow String messages?

I assume GlobalLoggy is as capable as any other loggy provided the implementation is just a new loggy and global variables. So why does logDebug for example only take string message and not all the parameters

Feedback: Inconvenient mixin API. Consider adding a global access to the logger.

Hi there. I tried to setup your logger and want to leave some feedback. I made a conclusion that it's inconvenient that we able to use it only with mixin. What should I do if I want to log something in a global scope? You should consider a possibility to use a logger without any mixin's. For example:

loggy.ui.info('some info from ui');

or

loggy.global.info('something from a global scope');

or even

loggy.info('something from a global scope');

Can`t see log prints in Futures

Hi,
first of all, i very much like this logger and the simplicity of creating new mixins for different object types.
Im using GetX package and when print a logger inside a Future method, i don't see those prints..
For example:
class AuthService extends GetxService with ServiceLogger {
AuthService({required this.authProvider});

final AuthProvider authProvider;
RxBool _userStateRegistered = false.obs;
RxBool get userStateRegistered => _userStateRegistered;

Future init() async {
loggy.info('Before register');
_userStateRegistered = userRegistered();
return this;
}
The print inside the Future is not printed to console..
what I'm I doing wrong?

Warning appears in console

../../../Downloads/flutter/.pub-cache/hosted/pub.dartlang.org/flutter_loggy-2.0.0/lib/flutter_loggy/stream_printer.dart:16:52: Warning: Operand of null-aware
operation '!' has type 'List<LogRecord>' which excludes null.
 - 'List' is from 'dart:core'.                                          
 - 'LogRecord' is from 'package:loggy/loggy.dart' ('../../../Downloads/flutter/.pub-cache/hosted/pub.dartlang.org/loggy-2.0.0+1/lib/loggy.dart').
    logRecord.add(<LogRecord>[record, ...logRecord.value!]);  

I don't mind ignoring it, but it kinda is annoying after a point. Considering the whole point of the package is to promote cleaner logs, moreover log stuff to stdout.

wrap width

If I am trying to log large json responses, the output is truncated, probably because there is a character limit in the terminal. If loggy could provide the functionality to wrap the lines at a given character/wrap length that would help in logging large string's. could also probably implement a replace function or possibly a formatted json output.

[flutter_loggy_dio] LoggyDioInterceptor default LogLevel

The LogLevel-s in LoggyDioInterceptor might be confusing.

The logging in onRequest (requestHeader, requestBody) has a LogLevel.debug.
The logging in onResponse (responseHeader, responseBody) has a LogLevel.info.

Is there a reason for having different LogLevels between request and response?
Ideally, I would like this to be customizable.

[Feature Request] It's will be nice if there is a possiblity to have the named of the function where the debug is call

Hello, when I saw you doc.
I see that:

[D] UI Loggy - DoSomeWork: This is debug message

I told to myself wow wonderfull we can see the named of the function.
But in fact, it was the named of the class.

So I look in the internet if it's possible to do that.
https://stackoverflow.com/questions/49966808/how-to-get-the-name-of-the-current-and-calling-function-in-dart

It's look very complicated.

So I just post that feature request maybe it's easy for you.

Nice package by the way !

Question

How can i modify LoggyStreamWidget() Page's "EXPAND" and "COLLAPSE"?

[loggy] Custom log level example

I have a question regarding the documentation example for Custom log levels and expanding existing printers.

Documentation Example:

extension SocketLevel on LogLevel {
  // LogLevel is just a class with `name` and `priority`. Priority can go from 1 - 99 inclusive.
  static const LogLevel socket = LogLevel('socket', 32);
}

What is the purpose of making an extension?
Someone less familiar with Dart might think that he can get the socket by calling LogLevel.socket, when in fact this won't work.

Improve docs

Just a few little improvements for READMEs.

  • Add one sentence explanation to begging of each package (similar to pubspec.yaml description).

Most people will use flutter_loggy or flutter_loggy_dio and think that's the link we're going to share and promote. I propose:

  • Either 1) Copy documentation from loggy or 2) Better link to it (big letters Documentation so it's more obvious).
  • Add link to flutter_loggy_dio in flutter_loggy:
    If you are using Dio, we suggest to use flutter_loggy_dio.
  • Add example of logs to flutter_loggy (just few lines) so people can get a feel for it

Support multiple printers

It would be nice to be able to log using multiple printers, for instance if I want to use the flutter printer to log to dart devtools, but also use the pretty printer to log in the terminal.

[Bug] PrettyDeveloperPrinter not working

OS: MacOS BigSur 11.5.2
Flutter app running on Kitty Terminal Emulator
testing on: iOS simulator iPhone 12 Pro Max

when I use PrettyDeveloperPrinter with iOS, nothing is being printed to console.
I have come across something similar with PrettyPrinter with showColors: true. On iOS, the showColor option doesn't color the output, instead just prints the escape characters as is.

Reset ansi color

There have been instances when the color of the log has leaked to other parts of the console, you would need to reset the ansi color by appending ^[[0m at the end. I use loggy along with print statement as well, I have noticed that the print statements have been printed to consoled colored as the previous loggy print.

PrettyDeveloperPrint

I can't find the extension or the implementation of this any where. I would like to know where I can find it, or even how I can implement it myself.

[pretty_loggy_dio] Can't pretty log Json/Map and almost every request runs into the print character limit

While using the LoggyDioInterceptor I noticed that my requests always triggered the try/catch block here.
The exception thrown is FormatException because

data.toString()

returns the Json without quotes which can't be parsed by

JsonDecoder().convert(...)

I'd suggest using the pretty_dio_logger approach to log as json/Map. The only major difference with pretty_dio_logger is it logs the response line by line instead of using line breaks as this plugin.
In my opinion using line breaks is superior because the calls can't be broken up by simultaneous logging calls.
However it leads to irritation because long calls (a small json is enough) are being cut of on Android when using the print method(like the DefaultPrinter()) instead of debugPrint(with maxWidth) or developer.log. I'd suggest adding some Info to the readme to stop confusion and issues like #45.

I'll see if I have the time to fix the json issue in the future and mainly opened this issue to get feedback on the linebreak vs print by line approach. What do you think?

_commit throws: Invalid value: Not in inclusive range

I encounter an error when I get an HTTP error where no \n is present.

flutter: RangeError (end): Invalid value: Not in inclusive range 0..154: -1;#0      RangeError.checkValidRange (dart:core/errors.dart:379:9)
flutter: #1      _StringBase.substring (dart:core-patch/string_patch.dart:398:27)
flutter: #2      LoggyDioInterceptor._commit
dio_logger.dart:174
flutter: #3      LoggyDioInterceptor.onError
dio_logger.dart:97
flutter: #4      DioMixin.fetch.errorInterceptorWrapper.<anonymous closure>.<anonymous closure>
dio_mixin.dart:463
flutter: #5      new Future.<anonymous closure> (dart:async/future.dart:253:37)
flutter: #6      _rootRun (dart:async/zone.dart:1390:47)
flutter: #7      _CustomZone.run (dart:async/zone.dart:1300:19)
flutter: #8      _CustomZone.runGuarded (dart:async/zone.dart:1208:7)
flutter: #9      _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1248:23)
flutter: #10     _rootRun (dart:async/zone.dart:1398:13)
flutter: #11     _CustomZone.run (dart:async/zone.dart:1300:19)
flutter: #12     _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:1232:23)
flutter: #13     Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
flutter: #14     _Timer._runTimers (dart:isolate-patch/timer_impl.dart:398:19)
flutter: #15     _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:429:5)
flutter: #16     _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:192:26)

Issue is the line 174 in LoggyDioInterceptor (dio_logger.dart):

final String errorTitle = valueError.substring(0, valueError.indexOf('\n'));

When valueError.indexOf returns -1 it results in an error.

valueError => "<<< DioError โ”‚ GET โ”‚ 401 Unauthorized โ”‚ https://redacted.azurecontainerapps.io/api/v1/some/apiEndpoint"

Would be great to have this checked and fixed as it crashes dio.

null-safety

any plans to address null-safety compatibility?

Broken collapsing in 3.1.0

One of the features I really like is broken now: https://github.com/infinum/floggy?tab=readme-ov-file#setup-1

I think because the latest release introduced more of โ•โ• lines, which now don't collapse nicely. But I'm not sure, didn't dig into it.:

Example of nicely collapsed:
image

From the new release, the request is not collapsed:
image

I suggest using like ----- instead of โ•โ•โ• if you want delimiters inside the request.

Can this be used outside of a class?

I'm building a CLI tool at the moment, and I have some helper functions in separate files, not contained inside classes themselves...

I've got this working perfectly in my main application logic, but I'm battling to use it inside standalone functions. any suggestions here?


edit: just to clarify, I'm able to use log (and it's level-specifc variants) after callling Loggy.initLoggy() inside my individual functions.. but is the idea that each of these helper functions needs it's own logger then?

or should I perhaps create one helper function to wrap this initialisation, and then just call that from my other functions..? hmmm.. sorry for the potentially stupid questions, big time Dart newbie over here.. ๐Ÿ‘‹๐Ÿผ

Not pretty priting if using dio to decode

When using

    final response = await _dio.get<List<dynamic>>(
      ApiEndpoints.tournaments,
    );

it would not print nice but all will be printed in one line.

Reason:
In the case dio is decoding json, the dio_logger will get string like [{id: 1}, {id: 2}]. Note the missing "" from json. And it would crash with FormatException on line 129 and go in catch clause:

    try {
      final Object object = const JsonDecoder().convert(data.toString());
      const JsonEncoder json = JsonEncoder.withIndent('  ');
      value = 'โ•‘  ${json.convert(object).replaceAll('\n', '\nโ•‘  ')}';
    } catch (e) {
      value = 'โ•‘  ${data.toString().replaceAll('\n', '\nโ•‘  ')}';
    }

Workaround is to always get the string and decode it by yourself:

    final response = await _dio.get<String>(
      ApiEndpoints.tournaments,
    );

dio_logger error at web environment.

dio_logger emit error on XMLHttpRequest error of web environment when using flutter_loggy_dio.

XMLHttpRequest error is derived from CORS issue when launching flutter web environment (especially when debugging).

And in this situation, if I use dio logging with flutter_loggy_dio, the error log is not printed properly and even for the exception path because it crashed at logging stage rather than network error.

at dio_logger.dart,

_value was "<<< DioError โ”‚ GET โ”‚ null null โ”‚ null"

crash point in logger was on flutter_loggy_dio/diop_logger.dart: 166

  void _commit(LogLevel level) {
    if (level.priority >= LogLevel.error.priority) {
      final String _valueError = _value.toString();
      final String _errorTitle = _valueError.substring(0, _valueError.indexOf('\n')); // this line
      final String _errorBody = _valueError.substring(_errorTitle.length);
      loggy.log(level, _errorTitle, _errorBody);
    } else {
      loggy.log(level, _value.toString());
    }
    _value.clear();
  }

and error was

22:12:24.742: [E] packages/ming/main.dart 36:12 in <fn>: Error: Uncaught Error.
RangeError (end): Invalid value: Not in inclusive range 0..37: -1
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 251:49  throw_
dart-sdk/lib/core/errors.dart 356:9                                           checkValidRange
dart-sdk/lib/_internal/js_dev_runtime/private/js_string.dart 168:22           substring]
packages/flutter_loggy_dio/flutter_loggy_dio/dio_logger.dart 166:45           [_commit]
packages/flutter_loggy_dio/flutter_loggy_dio/dio_logger.dart 92:5             onError$
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 84:54            runBody
dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 123:5            _async
packages/flutter_loggy_dio/flutter_loggy_dio/dio_logger.dart 74:15            onError
packages/dio/src/dio_mixin.dart 574:28                                        <fn>
packages/dio/src/dio_mixin.dart 789:22                                        checkIfNeedEnqueue
packages/dio/src/dio_mixin.dart 572:22                                        <fn>
dart-sdk/lib/async/future.dart 252:37                                         <fn>
dart-sdk/lib/async/zone.dart 1418:47                                          _rootRun
dart-sdk/lib/async/zone.dart 1328:19                                          run
dart-sdk/lib/async/zone.dart 1236:7                                           runGuarded
dart-sdk/lib/async/zone.dart 1276:23                                          <fn>
dart-sdk/lib/async/zone.dart 1426:13                                          _rootRun
dart-sdk/lib/async/zone.dart 1328:19                                          run
dart-sdk/lib/async/zone.dart 1260:23                                          <fn>
dart-sdk/lib/_internal/js_dev_runtime/private/isolate_helper.dart 48:19       internalCallback

Null type error while running tests

Ran into an issue while running tests with Floggy enabled. I tracked it down to a hard cast on a null value in the StreamPrinter. Ill create a PR that will remove that exclamation mark.

../../../.pub-cache/hosted/pub.dartlang.org/flutter_loggy-2.0.0/lib/flutter_loggy/stream_printer.dart:16:52: Warning: Operand of null-aware operation '!' has type 'List<LogRecord>' which excludes null.
 - 'List' is from 'dart:core'.
 - 'LogRecord' is from 'package:loggy/loggy.dart' ('../../../.pub-cache/hosted/pub.dartlang.org/loggy-2.0.0/lib/loggy.dart').
    logRecord.add(<LogRecord>[record, ...logRecord.value!]);

[Feature suggestion] class name and method name access with global loggy.

Hi, I recently found ur library and it seems great.

But, personally I wanna use global loggy than use mixin for log.

And I think global loggy also can support to get class name and method name for the log.
(I'm already using this with Logger library for the previous project.)

var member = Trace.current().frames[2].member!.split(".");

var className = member.length == 2 ? member[0] : ""; // if there are no corresponding class, then class name will be ""
var methodName = member.length == 2 ? member[1] : member[0];

how about support this feature?

Set custom trace level in options

I have abstract layer of logger service and use loggy like:

class LoggyLogger extends AppLoggerService with UiLoggy {
  @override
  void debug(dynamic message, [Object? error, StackTrace? stackTrace]) {
    loggy.debug(message, error, stackTrace);
  }

But in this way i can`t use includeCallerInfo (

FR: ringbuffer of log messages and push to server

For debugging app's in the wild, it would be convenient to log floggy alls logs into a ring-buffer and have a feature to upload a zipped archive of these buffered lined onto a server.

Is such a behavior maybe already implemented? Or is there another best practice to get the logs of remote devices?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.