Coder Social home page Coder Social logo

google / horologist Goto Github PK

View Code? Open in Web Editor NEW
524.0 14.0 81.0 69.73 MB

Horologist is a group of libraries that aim to supplement Wear OS developers with features that are commonly required by developers but not yet available.

Home Page: https://google.github.io/horologist/

License: Apache License 2.0

Shell 0.16% Kotlin 99.80% Starlark 0.01% Java 0.02%
wear-os compose media3

horologist's Introduction

Horologist logo

Horologist is a group of libraries that aim to supplement Wear OS developers with features that are commonly required by developers but not yet available.

Maintained Versions

The currently maintained branches of Horologist are.

Version Branch Min SDK (Wear) Description
0.5.x release-0.5.x 25 Wear Compose 1.3.x, Compose 1.6.x, Media3 and some betas of Androidx.
0.6.x main 26 Wear Compose 1.4.x, Compose 1.7.x and generally latest relevant alphas of Androidx.

Maintenance branches will not delete existing APIs and they should remain stable. However the main branch will actively update to incorporate new API guidance, removing or changing APIs.


๐ŸŽต Media

Horologist provides the Media Toolkit: a set of libraries to build Media apps on Wear OS and a sample app that you can run to see the toolkit in action.

The toolkit includes:

  • horologist-media-ui: common media UI components and screens like PlayerScreen.
  • horologist-media: domain model for Media related functionality. Provides an abstraction to the UI module (horologist-media-ui) that is agnostic to the Player implementation.
  • horologist-media-data: implementation of the domain module (horologist-media) using Media3.
  • horologist-media3-backend: Player on top of Media3 including functionalities such as avoiding playing music on the watch speaker.
  • media sample: sample app to listen to downloaded music.
Player Screen Browse Screen Entity Screen

๐Ÿ“… Composables

High quality prebuilt composables, such as Time and Date pickers.

DatePicker TimePickerWith12HourClock TimePicker
SegmentedProgressIndicator SquareSegmentedProgressIndicator

๐Ÿ“ Compose Layout

Layout related functionality such as a Navigation Aware Scaffold.

fillMaxRectangle()

๐Ÿ”ฒ Compose Material

Opinionated implementation of the components of the Wear Material Compose library , based on the specifications of Wear Material Design Kit .

๐Ÿ”Š Audio and UI

Domain model for Audio related functionality. Volume Control, Output switching. Subscribing to a Flow of changes in audio or output.

VolumeScreen

๐Ÿ” Auth

Libraries to help developers to build apps following the Sign-In guidelines for Wear OS .

DataLayer

The Horologist DataLayer library, provide common abstractions on top of the Wearable DataLayer. It includes libraries to build prompts on the phone to improve engagement with the correspondent Wear app and a sample to see the prompts in actions. Find guidance in the project documentation.

โ˜ฐ Tiles

Kotlin coroutines flavoured TileService.

horologist-tiles


Why the name?

The name mirrors the Accompanist name, and is also Watch related.

https://en.wiktionary.org/wiki/horologist

horologist (Noun) Someone who makes or repairs timepieces, watches or clocks.

Contributions

Please contribute! We will gladly review any pull requests submitted. Make sure to read the Contributing page to know what our expectations of contributions are.

License

Copyright 2023 The Android Open Source Project

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

horologist's People

Contributors

anubhavkakkar avatar bowersteve avatar clickxu avatar dabluck avatar denors avatar fstanis avatar garanj avatar hardikagr avatar ithinkihaveacat avatar johnnichol avatar kpaikena avatar kpeved avatar kul3r4 avatar laiyichin avatar lorenznickel avatar louiscad avatar luizgrp avatar mohitmandalia avatar oas004 avatar plksharma30 avatar rajat4064g avatar renovate-bot avatar schmiphi avatar smashedfrenzy16 avatar ssancho14 avatar wrkben avatar yschimke avatar zach-klippenstein avatar zlandorf avatar zxzxwu 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  avatar  avatar  avatar  avatar  avatar  avatar

horologist's Issues

False positives by Paparazzi snapshot tests

Paparazzi snapshot tests does not fail when it should. I don't think it happens 100% of the time, but it's frequently enough.

Previously noticed in #292

Recently noticed after submitting this commit which removes all the usages of WearSnapshotHandler. So all screenshots went from circle to square. And the snapshots tests passed. We can see in the logs in CI that most of the tasks are marked as "UP-TO-DATE" when running verifyPaparazziDebug:

> Task :media-ui:testDebugUnitTest UP-TO-DATE
> Task :media-ui:verifyPaparazziDebug UP-TO-DATE

Potentially related to cashapp/paparazzi#388.

WearNavScaffold timeText is problematic

The mandatory modifier it is clumsy, we should apply the scaling externally, say in a containing box.

        WearNavScaffold(
            modifier = modifier,
            startDestination = NavigationScreens.Player.route + "?page={page}",
            timeText = {
                    TimeText(modifier = it)
            },

Change implementation of SecondaryPlaceholderChip to use Chip

WHAT

Change implementation of SecondaryPlaceholderChip to use Chip instead of building everything from scratch, and pass the grey shapes as params to label, secondaryLabel, and icon.

PS: remember to remove this dependency from the module when done.

WHY

#328 (comment)

Expected outcome from this task

  • Implementation of SecondaryPlaceholderChip is changed to use Chip;
  • Preview for SecondaryPlaceholderChip is not affected;
  • Snapshot test for SecondaryPlaceholderChip is not affected;

WearNavScaffold doesn't work if scrollStateBuilder and AutoCenteringParams conflict

This setup doesn't work because the list can never be in the position where it should show time text.

    WearNavScaffold(
        startDestination = NavScreen.Menu.route,
        navController = navController,
        // state = navState
    ) {
        scalingLazyColumnComposable(
            route = NavScreen.Menu.route,
            scrollStateBuilder = { ScalingLazyListState(initialCenterItemIndex = 0) }
        ) {
    ScalingLazyColumn(
        modifier = modifier.scrollableColumn(focusRequester, scrollState),
        state = scrollState,
        horizontalAlignment = Alignment.CenterHorizontally,
        autoCentering = AutoCenteringParams(itemIndex = 1, itemOffset = 30),
    ) {

Changing autocentering to itemIndex to be 0 works, so this isn't a critical bug.

But also the fix to scrollStateBuilder isn't great either, as it doesn't work for 1/30, but 2,-30 does???

scrollStateBuilder = { ScalingLazyListState(initialCenterItemIndex = 2, initialCenterItemScrollOffset = -30) }

network-selection library

Network selection library for efficient, correct and validated network selection logic in 3P apps.

Uplift the network selection logic from the media reference app.

/**
 * Implementation of app rules for network usage.  A way to implement logic such as
 * - Don't download large media items over BLE.
 * - Only use LTE for downloads if user enabled.
 * - Don't use metered LTE for logs and metrics.
 */
interface NetworkingRules {
    /**
     * Is this request considered high bandwidth and should activate LTE or Wifi.
     */
    fun isHighBandwidthRequest(requestType: RequestType): Boolean

    /**
     * Checks whether this request is allowed on the current network type.
     */
    fun checkValidRequest(
        requestType: RequestType,
        currentNetworkType: NetworkType
    ): RequestCheck

    /**
     * Returns the preferred network for a request.
     */
    fun getPreferredNetwork(
        networks: Networks,
        requestType: RequestType
    ): NetworkStatus?
class NetworkRepository(
    val connectivityManager: ConnectivityManager,
    val coroutineScope: CoroutineScope,
    val logger: NetworkStatusLogger,
) {
...
    val networkStatus: MutableStateFlow<Networks>

Add screenshot tests

The bugs, both caused by framework changes, and 1 I introduced myself show we need more test coverage here.

Add appropriate icon for SeekFowardButton

WHAT

Add appropriate icon for SeekFowardButton for when seekButtonIncrement param is SeekButtonIncrement.Unknown.

WHY

In order to match the icon displayed for SeekBackButton:

SeekBackButton SeekForwardButton
Screen Shot 2022-04-13 at 17 02 36 Screen Shot 2022-04-14 at 09 59 26

There is no matching icon in the default material design icon lib.

Paparazzi reports not being archived in CI

After execution of this step in CI we can see in the logs like:

> Task :composables:testDebugUnitTest
See the Paparazzi report at: file:///home/runner/work/horologist/horologist/composables/build/reports/paparazzi/index.html

But the artifacts generated by CI never contain these reports, even after explicitly adding a configuration to archive them in #337.

Change UI State concept of player

See #286 (comment)

given the UI layer has its own model PlayerUiState, it could be changed to attend the specific needs of the UI classes, e.g. playerUiState.loading instead of playerUiState.connected; or playerUiState.mediaDisplayStatus be either of values Loading, Playing or NotPlaying.

Use modes like playing, loading etc.

App crashes after going back from Audio output screen

Media sample app freezes and crashes after going back (swipe back without selecting any output) from Audio output screen. This only happens if the Audio output screen is launched after pushing the "play" button on the player screen. If the Audio output button is pushed instead, the issue does NOT happen.

Video:

crash_after_playing.mp4

Logs:

2022-06-27 10:42:51.090  6644-6644  InputMethodManager      com....horologist.mediasample.debug  I  startInputInner - mService.startInputOrWindowGainedFocus
2022-06-27 10:42:53.000   578-7953  ActivityManager         pid-578                              E  ANR in com.google.android.horologist.mediasample.debug (com.google.android.horologist.mediasample.debug/com.google.android.horologist.mediasample.components.MediaActivity)
                                                                                                    PID: 6644
                                                                                                    Reason: Input dispatching timed out (48d7fbd com.google.android.horologist.mediasample.debug/com.google.android.horologist.mediasample.components.MediaActivity (server) is not responding. Waited 10004ms for FocusEvent(hasFocus=false))
                                                                                                    Parent: com.google.android.horologist.mediasample.debug/com.google.android.horologist.mediasample.components.MediaActivity
                                                                                                    Load: 22.88 / 22.51 / 11.03

Device: Galaxy Watch4 Classic (4W2J) - SM-R895F

Steps to reproduce

  1. Make sure the watch does not have any bluetooth device connected
  2. Launch app - player screen is displayed with a media
  3. Push play - bluetooth settings screen is displayed
  4. Swipe back

Expected result

Player screen is displayed, in an idle/paused state, and app is not frozen.

Actual result

Player screen is displayed, in an idle/paused state, but app is frozen (nothing happens when tapping on any buttons) and after few seconds the app closes (crashes).

NPE in SnapshotVerifier during tests

Most of the tests are failing in CI with:

java.lang.NullPointerException: goldenImage must not be null
	at app.cash.paparazzi.SnapshotVerifier$newFrameHandler$1.handle(SnapshotVerifier.kt:53)
	at com.google.android.horologist.paparazzi.WearSnapshotHandler$newFrameHandler$1.handle(WearSnapshotHandler.kt:57)
	at app.cash.paparazzi.Paparazzi$takeSnapshots$1$2.invoke(Paparazzi.kt:302)
	at app.cash.paparazzi.Paparazzi$takeSnapshots$1$2.invoke(Paparazzi.kt:295)
	at app.cash.paparazzi.Paparazzi.withTime(Paparazzi.kt:336)
	at app.cash.paparazzi.Paparazzi.takeSnapshots(Paparazzi.kt:295)
	at app.cash.paparazzi.Paparazzi.snapshot(Paparazzi.kt:211)
	at app.cash.paparazzi.Paparazzi.snapshot(Paparazzi.kt:204)
	at app.cash.paparazzi.Paparazzi.snapshot$default(Paparazzi.kt:195)
	at com.google.android.horologist.media.ui.FigmaVolumeScreenTest.volumePlayerScreen(FigmaVolumeScreenTest.kt:46)

The stacktrace above can be seen in the test reports in the archived artifact. In the actions logs it displays the following:

com.google.android.horologist.media.ui.FigmaVolumeScreenTest > volumePlayerScreen FAILED
    java.lang.NullPointerException at FigmaVolumeScreenTest.kt:46

...

53 tests completed, 35 failed
> Task :media-ui:testDebugUnitTest FAILED

Some investigation reported here.

Usage of WearSnapshotHandler was removed in #338 for now.

DatePicker day position issue

Issue

The day picker is not keeping the position of the previously selected day when going to the month picker and coming back to the day picker.

Steps to reproduce

There are few ways of reproducing it, as you can see in the gif attached below, but here is one:

  1. Pick Jan as month
  2. Pick 31 as day
  3. Pick Feb as month; day should change automatically to 28
  4. Go back to day picker; do not scroll or pick another day
  5. Pick Jan as month
  6. Go back to day picker
Expected result

Day picker should display day 28.

Actual result

Day picker displays day in between 30 and 31.

Video

picker glitch

Bluetooth settings screen does not stay open when there is a connected device

When launched from the Media sample app, the bluetooth settings screen does not stay open when there is a connected device.

cannot_open_audiooutput_screen.mp4

Devices:

  • Galaxy Watch4 Classic (4W2J) - SM-R895F
  • Galaxy Buds2 - SM-R177

Steps to reproduce

  1. Make sure the watch has a bluetooth device connected to it
  2. Launch app - player screen is displayed with a media
  3. Push Audio output button

Expected result

Bluetooth settings screen is displayed.

Actual result

Bluetooth settings screen is launched and closed immediately.

Add support for WEBP or PNG with transparency to Tiles image helpers

The current conversion code for bitmaps and canvas elements to Tile ImageResources, uses RGB_565.

public fun Bitmap.toImageResource(): ImageResource {

This is safe format, but requires extra work and also does not support alpha channel.

We should add a flag to support other formats including png, webp.

This probably involves removing hard coded format when flag is set

setFormat(ResourceBuilders.IMAGE_FORMAT_RGB_565)

and use, or other conversion code.

    val bytes = ByteArrayOutputStream().apply {
        compress(Bitmap.CompressFormat.PNG, 100, this)
    }.toByteArray()

We can assume Coil for our conversion code if it helps, and to start with avoiding conversions, but to be confirmed.

A later change might be also this code path https://github.com/google/horologist/blob/2f15272f92291bcdbdacd66b1042e42802c45cb7/tiles/src/main/java/com/google/android/horologist/tiles/canvas/canvas.kt

Make Seconds optional in TimePickers

Time pickers have hours minutes and seconds currently with no way to disable seconds - make seconds optional as hours & minutes and hours, minutes and seconds are both useful combinations

Signing process doesn't work on OSX

$ release/signing-setup.sh 'xxxx'
usage: enc -ciphername [-AadePp] [-base64] [-bufsize number] [-debug]
    [-in file] [-iv IV] [-K key] [-k password]
    [-kfile file] [-md digest] [-none] [-nopad] [-nosalt]
    [-out file] [-pass arg] [-S salt] [-salt]

 -A                 Process base64 data on one line (requires -a)
 -a                 Perform base64 encoding/decoding (alias -base64)

Add Id to ImageResource mapper

fun idToImageResource(id: Int) = ImageResource.Builder()
       .setAndroidResourceByResId(
               AndroidImageResourceByResId.Builder()
                       .setResourceId(id)
                       .build()
       )
       .build()

From here:

https://github.com/joreilly/PeopleInSpace/pull/139/files#diff-4058974ed1ff8fbb817967ab294ee5a8b422543b015c50cf3481cc2fddffbe0cR119-R126

I'm using it for a component audit (in combination with LayoutPreview) but presumably it's handy for the tile renderer too.

Notes:

  • consider adding @DrawableRes to the function

Failing SnackbarHost test

com.google.android.horologist.compose.snackbar.SnackbarHostTest > snackbarHost_returnedResult[test(AVD) - 11] FAILED 
	androidx.compose.ui.test.ComposeTimeoutException: Condition still not satisfied after 5000 ms
	at androidx.compose.ui.test.AndroidComposeTest.waitUntil(ComposeTest.android.kt:218)

Have disabled for now in #65

Broken or Flaky tests

Umbrella bugs for Flaky or Broken tests on emulators.

We should look at how to implement non-flaky, such as Robolectric for most tests.

Picker a11y tests

Follow up on #90

  • provide way of getting the value
  • minimal tests
  • API for onChange without submitting
  • Remove any non localised content. AM/PM. hours, minutes, seconds.
  • Review APIs
  • Screenshot tests
  • Theming, customising (date range, error states)
  • a11y review + tests

Fix VolumeScreenIndividualTest and VolumeScreenThemeTest

After merging #322, build step in main branch got green.

However, the PRs that are up to date with main branch started to fail with:

com.google.android.horologist.audio.ui.VolumeScreenIndividualTest > volumeScreenAtMaximum FAILED
    java.lang.NullPointerException at VolumeScreenIndividualTest.kt:66
com.google.android.horologist.audio.ui.VolumeScreenIndividualTest > volumeScreenAtMinimum FAILED
    java.lang.NullPointerException at VolumeScreenIndividualTest.kt:49
com.google.android.horologist.audio.ui.VolumeScreenIndividualTest > volumeScreenWithLongTest FAILED
    java.lang.NullPointerException at VolumeScreenIndividualTest.kt:83

Once again, main is green, but the PRs that are up to date with main branch started to fail with:

com.google.android.horologist.audio.ui.VolumeScreenThemeTest > volumeScreenThemes[0] FAILED
    java.lang.NullPointerException at VolumeScreenThemeTest.kt:56
com.google.android.horologist.audio.ui.VolumeScreenThemeTest > volumeScreenThemes[1] FAILED
    java.lang.NullPointerException at VolumeScreenThemeTest.kt:56
com.google.android.horologist.audio.ui.VolumeScreenThemeTest > volumeScreenThemes[2] FAILED
    java.lang.NullPointerException at VolumeScreenThemeTest.kt:56
com.google.android.horologist.audio.ui.VolumeScreenThemeTest > volumeScreenThemes[3] FAILED
    java.lang.NullPointerException at VolumeScreenThemeTest.kt:56
com.google.android.horologist.audio.ui.VolumeScreenThemeTest > volumeScreenThemes[4] FAILED
    java.lang.NullPointerException at VolumeScreenThemeTest.kt:56
com.google.android.horologist.audio.ui.VolumeScreenThemeTest > volumeScreenThemes[5] FAILED
    java.lang.NullPointerException at VolumeScreenThemeTest.kt:56
10 tests completed, 7 failed, 3 skipped
com.google.android.horologist.audio.ui.VolumeScreenThemeTest > volumeScreenThemes[6] FAILED
    java.lang.NullPointerException at VolumeScreenThemeTest.kt:56

CompoundPicker

Implement a base class for a CompoundPicker handling most of the complexity of Data and Time Pickers and allowing for simple implementations of others.

Tile Preview unable to render ProgressIndicatorLayout

Preview Emulator
image image

The other previews are fine:

image


The code for the layout:

@WearLargeRoundDevicePreview
@Composable
fun Cir() {
    val context = LocalContext.current
    val deviceParameters = buildDeviceParameters(context.resources)
    LayoutPreview(
        ProgressIndicatorLayout.Builder(deviceParameters)
            .setPrimaryLabelTextContent(
                Text.Builder(context, "Steps")
                    .setTypography(Typography.TYPOGRAPHY_CAPTION1)
                    .setColor(
                        ColorBuilders.argb(
                            ContextCompat.getColor(
                                context,
                                R.color.primary
                            )
                        )
                    )
                    .build()
            )
            .setSecondaryLabelTextContent(
                Text.Builder(context, "5168")
                    .setTypography(Typography.TYPOGRAPHY_CAPTION1)
                    .setColor(
                        ColorBuilders.argb(
                            ContextCompat.getColor(
                                context,
                                R.color.white
                            )
                        )
                    )
                    .build()
            )
            .setProgressIndicatorContent(
                CircularProgressIndicator.Builder()
                    .setProgress(5168f/8000)
                    .setCircularProgressIndicatorColors(
                        ProgressIndicatorColors(
                            /* indicatorColor = */ ColorBuilders.argb(ContextCompat.getColor(context, R.color.primary)),
                            /* trackColor = */ ColorBuilders.argb(Color(1f, 1f, 1f, 0.1f).toArgb())
                        )
                    )
                    .build()
            )
            .setContent(
                Text.Builder(context, "/ 8000")
                    .setTypography(Typography.TYPOGRAPHY_DISPLAY1)
                    .setColor(
                        ColorBuilders.argb(
                            ContextCompat.getColor(
                                context,
                                R.color.white
                            )
                        )
                    )
                    .build()
            )
            .build()
    )
}

Horologist: 0.0.23
Tiles Material Components: https://androidx.dev/snapshots/builds/8730571/artifacts

Media title is being trimmed

On the Media sample app, the media title is being trimmed on the left side:

Screenshot_20220627_113805

Screenshot_20220627_113814

Devices:

  • Galaxy Watch4 Classic (4W2J) - SM-R895F
  • Emulator - Wear OS Large Round API 30

The issue become apparent on the PlayerScreen preview once the title length is increased:

Screen Shot 2022-06-27 at 11 53 38

Resolve on screen content with translations

See #286 (comment)

nit: I see the benefit of the library providing default content descriptions to the icons it displays (which can be overridden to provide translations) but I feel that displaying a text on the screen in a specific language by default is more invasive and would rather require this as parameter to the component - I might be biased and maybe I should not see any difference in between these two scenarios

StandardChip placeholder icon color not changing when disabled

StandardChip placeholder icon should have an alpha applied in order to change the color when on disabled state.

Compare:

Screen Shot 2022-07-11 at 15 17 40

Our implementation relies on Chip from androidx.wear.compose.material which seems to implement this functionality. From the kdoc:

In order to correctly render when the Chip is not enabled the icon must set its alpha value to [LocalContentAlpha].

However, our implementation uses Coil's rememberAsyncImagePainter to display a placeholder, which receives a Painter type as placeholder, and might not work well with that alpha applied in Chip.

Expected outcome from this task

  • PrimaryChip preview for disabled state displays placeholder icon with correct alpha applied
  • SecondaryChip preview for disabled state displays placeholder icon with correct alpha applied
  • Other previews for PrimaryChip and SecondaryChip are not affected
  • PrimaryChip snapshot test for disabled state displays placeholder icon with correct alpha applied
  • SecondaryChip snapshot test for disabled state displays placeholder icon with correct alpha applied
  • Other snapshot tests for PrimaryChip and SecondaryChip are not affected

Do not run tests with LargeTest annotation in CI

WHAT

Do not run tests with LargeTest annotation in CI.

WHY

It was the intention of #304

However, it looks like they are still running as this recent PR failed with:

com.google.android.horologist.mediasample.playback.PlaybackNotificationTest > testCausesNotification[test(AVD) - 9] FAILED 
	expected to be true
	at com.google.android.horologist.mediasample.playback.PlaybackNotificationTest$testCausesNotification$1$1.invokeSuspend(PlaybackNotificationTest.kt:63)
Tests on test(AVD) - 9 failed: There was 1 failure(s).

And the test that failed is annotated with LargeTest.

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.