Comments (15)
I make it work in my customized install, but I've also done a lot to differentiate my install from the base code in this project.
I'm not going to tell you it's easy, but I'll try and walk through it below.
I also can't promise you a be-all, end-all solution because mine tears apart a lot of what's in the 128x64 code and doesn't necessarily even try to respect other matrix sizes.
Here are some pieces that might help, though:
- I created a new data class called "AtBat" with a file named
atbat.py
in thedata
directory that uses the newerstatsapi
functionality. This class gives functions to access a name for the current batter, pitcher, and on-deck and in-the-hole batters from thestatsapi
linescore for a given game. It also formats those names to have just have first initials and the last name, unless the last name is particularly long, then it just shows last name. There's not a lot to it, honestly:
import statsapi
class AtBat:
def __init__(self, linescore):
self.batter = str(linescore.get('offense').get('batter',{}).get('fullName',{}))
self.pitcher = str(linescore.get('defense').get('pitcher',{}).get('fullName',{}))
self.onDeck = str(linescore.get('offense').get('onDeck',{}).get('fullName',{}))
self.inHole = str(linescore.get('offense').get('inHole',{}).get('fullName',{}))
def get_batter(self):
return format_statsapi_name(self, self.batter)
def get_pitcher(self):
return format_statsapi_name(self, self.pitcher)
def get_onDeck(self):
return format_statsapi_name(self, self.onDeck)
def get_inHole(self):
return format_statsapi_name(self, self.inHole)
def format_statsapi_name(self, name):
SpcPos = name.find(" ") + 1
playerLast = name[SpcPos:len(name)]
if name:
if len(playerLast) >= 9:
return playerLast
elif name[1] == ".":
return name
elif name == "{}":
return " "
else:
return name[0] + ". " + playerLast
else:
return " "
- I then have an "AtBatRenderer" class in its own
atbat.py
file in therenderers
directory that utilizes the AtBat class functions and renders that data to the board on the in-game scoreboard. This draws the current batter with "AB: " in front of their name and pitcher with "P:" in front of their name.
from rgbmatrix import graphics
from utils import get_font, get_file
import json
class AtBatRenderer:
"""Renders the batter and pitcher."""
def __init__(self, canvas, atbat, data):
self.canvas = canvas
self.batter = atbat.get_batter()
self.pitcher = atbat.get_pitcher()
self.data = data
self.colors = data.config.scoreboard_colors
self.default_colors = self.data.config.team_colors.color("default")
self.bgcolor = self.colors.graphics_color("default.background")
def render(self):
self.batter_coords = self.data.config.layout.coords("atbat.batter")
self.pitcher_coords = self.data.config.layout.coords("atbat.pitcher")
self.__render_batter_text(self.batter, self.default_colors, self.batter_coords["x"], self.batter_coords["y"])
self.__render_pitcher_text(self.pitcher, self.default_colors, self.pitcher_coords["x"], self.pitcher_coords["y"])
def __render_batter_text(self, batter, colors, x, y):
text_color = self.default_colors['text']
text_color_graphic = graphics.Color(text_color['r'], text_color['g'], text_color['b'])
font = self.data.config.layout.font("atbat.batter")
batter_text = "AB: " + batter
graphics.DrawText(self.canvas, font["font"], x, y, text_color_graphic, batter_text)
def __render_pitcher_text(self, pitcher, colors, x, y):
text_color = self.default_colors['text']
text_color_graphic = graphics.Color(text_color['r'], text_color['g'], text_color['b'])
font = self.data.config.layout.font("atbat.pitcher")
pitcher_text = "P: " + pitcher
graphics.DrawText(self.canvas, font["font"], x, y, text_color_graphic, pitcher_text)
- This does require the addition of an "atbat" section to the
ledcoords/w128h64.json
file:
"atbat": {
"batter": {
"font_name": "10x11",
"x": 2,
"y": 54
},
"pitcher": {
"font_name": "10x11",
"x": 8,
"y": 63
}
},
Adjust your font and where you want to put the current batter/pitcher accordingly. Mine looks like this:
-
This also will require an import and function call in the
renderers/scoreboard.py
file. You will need to duplicate the
from renderers.bases import BasesRenderer
andBasesRenderer(self.canvas, self.scoreboard.bases, self.data).render()
lines and replace "bases" or "Bases" with "atbat" or "AtBat" where appropriate. -
But what about the on-deck and in-the-hole batters? They have a place, notably on the between-innings screen. In the
renderers/inning.py
file, I've modified the__render_inning_break
function to use the "current" batter, on-deck batter and in-the-hole batter to create a "due up" screen.
def __render_inning_break(self):
text_font = self.layout.font("inning.break.text")
num_font = self.layout.font("inning.break.number")
text_coords = self.layout.coords("inning.break.text")
num_coords = self.layout.coords("inning.break.number")
color = self.colors.graphics_color("inning.break.text")
text = self.inning.state
num = self.inning.ordinal()
if text == "Middle":
num_x = 19
printtext = "Mid"
elif text == "End":
num_x = 24
printtext = "End"
graphics.DrawText(self.canvas, text_font["font"], 2, num_coords["y"], color, printtext)
graphics.DrawText(self.canvas, num_font["font"], num_x, num_coords["y"], color, num)
graphics.DrawText(self.canvas, num_font["font"], 2, num_coords["y"] + 13, color, "Due Up:")
graphics.DrawText(self.canvas, text_font["font"], 55, num_coords["y"] -3, color, self.atbat.get_batter())
graphics.DrawText(self.canvas, text_font["font"], 55, num_coords["y"] + 8, color, self.atbat.get_onDeck())
graphics.DrawText(self.canvas, text_font["font"], 55, num_coords["y"] + 19, color, self.atbat.get_inHole())
Granted, I got a little lazy and hard-coded some coordinates. I mentioned I tore the code apart pretty badly without mind for making it work for different board sizes. But it's another plausible use of that data.
This is just one way to get to & use the current hitter & pitcher data. There are others. But, like I said before, I'm not sure you can get to more than just fullName
. As nice as it would be to go out and get stats or the hitter's number or whatever, that's not in the linescore info, which means you have to go out and get it from a different, larger, slower table, like the player table, if you want it, and that's going to really drag your board's operation to a crawl, maybe making it sit on one game for upwards of a minute. With mine, it also flickered like the dickens while it tried to figure out what had to happen. Again, that player table is every player who's ever played in the bigs. That's a lot of data to load compared to a single game's linescore.
Anyhow, that's how I made it work. Apologies for using a sledgehammer to kill a fly, but that fly is also secretly more like a grizzly bear when you actually start to figure out how to kill it.
from mlb-led-scoreboard.
This is a good feature for 64x32.
The team score banners won't have enough room for anything else on a 32x32 and I wouldn't want to confuse the jersey numbers with the number of runs.
from mlb-led-scoreboard.
I'm not sure about number, but you can get and parse the fullName
of the batter and pitcher in the new API.
I have it in the code I've forked. I set up a separate data file called atbat
to handle it. I pull directly from MLB-StatsAPI.
I parse out first initial and last name. It's inelegant, but it works. I think number might be more difficult, though. You might be able to get to it by trying to link to their player database, but I found that to be a little too much for the Pi to handle without slowing down the whole process prohibitively. Remember that the player database, if I'm not mistaken, involves looking up a guy from every player who's ever played.
from mlb-led-scoreboard.
@pfightingpolish I was looking how to incorporate the batter and pitcher that is playing at that moment. Do you have the .py code or everything to make that work? i can dig on it but not that experienced. Thanks!
from mlb-led-scoreboard.
WOW thank you sooo muchhh!!!! I was working on my own way to get the atbat and was missing some things you have!! Obviously i will make it work on a 64x32 board i have but getting the batter and pitcher info is huge! Thank you again for this and your time!!!! I will tinker and understand what you did
from mlb-led-scoreboard.
Granted, my stuff above also assumes you already have some way of either translating between mlbgame
and statsapi
, or have fully converted all your data to statsapi
, and neither is what I'd call an "easy" feat.
I think much of this project's code still runs on mlbgame
, and I know my function to convert between the two is almost messy enough to make me not want to post it here. It includes what amounts to a lookup table to convert between team abbreviations from mlbgame
to team IDs from statsapi
. It really messes with data/data.py
.
So yeah, like I say, the above are hints, but there might still be a lot of steps on your journey.
from mlb-led-scoreboard.
Ahhh so your not using mlbgame. I am using mlbgame. I dont mind using statsapi for just the hitter and pitcher. I did see thag the mlbgame does have the atbat class i can call
from mlb-led-scoreboard.
Also, for your class atbat, where you have return_statsapi_name, should i just switch that to the mlbgame return? If that makes sense? Or should i just run statsapi for just this instance of atbat and run mlbgame for everything else
from mlb-led-scoreboard.
... sooooooo the big issue with trying to go back and forth between mlbgame
and statsapi
is they work completely differently. And when I say completely differently, I mean completely differently.
It's been nine months since I worked on this, but I'm pretty sure I remember that getting current batter through mlbgame
was either difficult or impossible. I remember trying to come up with some odd combination of adding batters faced by the pitching data in the boxscore to iterate my way through the lineup and eventually just giving up on it. Maybe there's something you found that I didn't, but yeah, it wasn't fun.
So that left me with trying to get the info from statsapi
, the newer API for MLB data.
Here's the problem, though: You have to connect the mlbgame
overview structure with the statsapi
linescore structure. That's not easy, because you essentially have to connect a game in mlbgame
, which uses an ID format of 2021_04_21_milmlb_sdnmlb_1
, with a game in statsapi
, which uses a numerical ID, like 634476
.
Now, again, it was nine months ago, but I think I remember seeing after the fact that there's actually an mlbgame
-style ID field endpoint somewhere in statsapi
, but not before I went about a more convoluted way of piecing the two together in data/data.py
:
- Parsing out the road team's abbreviation from the
mlbgame
ID. - Using a translation function to translate the
mlbgame
road team abbreviation to a correspondingstatsapi
team ID. - Using another function to handle doubleheader numbering in the
mlbgame
ID format. - Taking the
statsapi
road team ID and doubleheader game number and using that in the statsapi "schedule" function to get the appropriate corresponding numeric game ID instatsapi
. - Then using that numeric game ID to access the linescore function in
statsapi
.
Here's my modification of refresh_overview()
from data/data.py
, along with the helper functions I added, notably the team abbreviation translation table (I love how mlbgame
still refers to the Angels as "ANA"). Like I say ... not pretty, but it works.
def refresh_overview(self):
urllib.urlcleanup()
attempts_remaining = 5
while attempts_remaining > 0:
try:
self.overview = mlbgame.overview(self.current_game().game_id)
self.RoadTeam = self.get_road_abbrev()
self.RoadID = self.get_team_id(self.RoadTeam)
self.GameNum = self.get_game_num()
self.FunctionDate = datetime(self.year, self.month, self.day)
debug.log("Game ID: {}".format(self.current_game().game_id))
debug.log("Road Team: {}".format(self.RoadTeam))
debug.log("Road ID: {}".format(self.RoadID))
debug.log("Doubleheader Game Number: {}".format(self.GameNum))
debug.log("Date: {}".format(self.FunctionDate))
self.Stats_Game_ID = statsapi.schedule(datetime.strftime(self.FunctionDate, '%Y-%m-%d'), team=self.RoadID)[self.GameNum].get('game_id')
self.linescore = statsapi.get('game_linescore', {'gamePk': self.Stats_Game_ID})
self.__update_layout_state()
self.needs_refresh = False
self.print_overview_debug()
self.network_issues = False
break
except URLError, e:
self.network_issues = True
debug.error("Networking Error while refreshing the current overview. {} retries remaining.".format(attempts_remaining))
debug.error("URLError: {}".format(e.reason))
attempts_remaining -= 1
time.sleep(NETWORK_RETRY_SLEEP_TIME)
except ValueError:
self.network_issues = True
debug.error("Value Error while refreshing current overview. {} retries remaining.".format(attempts_remaining))
debug.error("ValueError: Failed to refresh overview for {}".format(self.current_game().game_id))
attempts_remaining -= 1
time.sleep(NETWORK_RETRY_SLEEP_TIME)
# If we run out of retries, just move on to the next game
if attempts_remaining <= 0 and self.config.rotation_enabled:
self.advance_to_next_game()
def get_game_num(self):
id_to_slice = self.current_game().game_id
TextNum = id_to_slice[-1:]
return int(TextNum) - 1
def get_road_abbrev(self):
id_to_slice = self.current_game().game_id
return id_to_slice[11:-12]
def get_team_id(self, team_abbrev):
print team_abbrev
if team_abbrev == "ari":
return 109
elif team_abbrev == "atl":
return 144
elif team_abbrev == "bal":
return 110
elif team_abbrev == "bos":
return 111
elif team_abbrev == "cha":
return 145
elif team_abbrev == "chn":
return 112
elif team_abbrev == "cin":
return 113
elif team_abbrev == "cle":
return 114
elif team_abbrev == "col":
return 115
elif team_abbrev == "det":
return 116
elif team_abbrev == "hou":
return 117
elif team_abbrev == "kca":
return 118
elif team_abbrev == "ana":
return 108
elif team_abbrev == "lan":
return 119
elif team_abbrev == "mia":
return 146
elif team_abbrev == "mil":
return 158
elif team_abbrev == "min":
return 142
elif team_abbrev == "nya":
return 147
elif team_abbrev == "nyn":
return 121
elif team_abbrev == "oak":
return 133
elif team_abbrev == "phi":
return 143
elif team_abbrev == "pit":
return 134
elif team_abbrev == "sdn":
return 135
elif team_abbrev == "sfn":
return 137
elif team_abbrev == "sea":
return 136
elif team_abbrev == "sln":
return 138
elif team_abbrev == "tba":
return 139
elif team_abbrev == "tex":
return 140
elif team_abbrev == "tor":
return 141
elif team_abbrev == "was":
return 120
elif team_abbrev == "nas":
return 160
elif team_abbrev == "aas":
return 159
else:
return 0
from mlb-led-scoreboard.
Ahh okay i didnt know they were completely different! All this stuff helps!! I will try to tackle it this week and see what i end up with. All of this help is fantastic!!! Here is what i found on the mlbgame atBat Its a class in the game events def. they have a list of what you can retrieve but not all the code. Just for pitches
http://panz.io/mlbgame/events.m.html#mlbgame.events.AtBat
def game_events(game_id)
class AtBat(object):
"""Class that holds information about at bats in games.
Properties:
away_team_runs
b
b1
b2
b3
batter
des
des_es
event
event_es
event_num
home_team_runs
num
o
pitcher
pitches
play_guid
s
start_tfs
start_tfs_zulu
"""
def __init__(self, data):
"""Creates an at bat object that matches the corresponding
info in `data`.
`data` should be a dictionary of values.
"""
# loop through data
for x in data:
# create pitches list if attribute name is pitches
if x == 'pitches':
self.pitches = []
for y in data[x]:
self.pitches.append(Pitch(y))
else:
# set information as correct data type
mlbgame.object.setobjattr(self, x, data[x])
def nice_output(self):
"""Prints basic at bat info in a nice way."""
return self.des
def __str__(self):
return self.nice_output()
from mlb-led-scoreboard.
@pfightingpolish If you've already solved this issue, can you just push a branch up to your forked copy and link us all the code?
@spaghettir101 If you want to do this in mlbgame
:
games = mlbgame.day(2021, 4, 21)
for game in games:
events = mlbgame.events.game_events(game.game_id)
Each of these would return a dictionary of innings shaped like this:
events = {
'1': {
'top': [
{
'tag': 'action',
'away_team_runs': '0',
'home_team_runs': '0',
'event_es': 'Game Advisory',
'event': 'Game Advisory',
'event_num': '1',
'des_es': 'Status Change - Pre-Game',
'des': 'Status Change - Pre-Game',
'o': '0',
's': '0',
'b': '0',
'tfs_zulu': '2021-04-21T17:24:57.577Z',
'tfs': '172457',
'pitch': '1',
'player': '624428'
},
{
'tag': 'atbat',
'num': '1',
'away_team_runs': '0',
'home_team_runs': '0',
'event_es': 'Game Advisory',
'event': 'Game Advisory',
'event_num': '2',
'des_es': 'Status Change - Pre-Game',
'des': 'Status Change - Pre-Game',
'p_throws': 'R',
'pitcher': '605242',
'b_height': '5\' 10"',
'stand': 'L',
'batter': '624428',
'end_tfs_zulu': '2021-04-21T17:24:57.577Z',
'start_tfs_zulu': '2021-04-21T17:24:57.577Z',
'start_tfs': '172457',
'o': '0',
's': '0',
'b': '0',
'pitches': []
}
],
'bottom': []
}
}
All those innings have a bunch of events in them, so you'd need to iterate through them all til you get to the last instance of an AtBat
. This is very expensive. Getting all the events for all the games takes about 10 seconds on my machine. There's also no player numbers here, I'm sure there's another mlbgame
call you could use.
from mlb-led-scoreboard.
@ty-porter Sure thing: Here's my fork.
Because solving the issue means altering much more than just a few lines of codes, and involves multiple files, and because my code has a lot of "other stuff" going on, I didn't think a simple link to the fork would do the trick. But yeah, it's mostly the ID translation in data/data.py
and adding the data/atbat.py
and render/atbat.py
files.
I was poking around with the game_events function when you posted, Ty, and I was coming to a similar conclusion that going through game_events was going to be problematic.
The big thing I get out of game_events is those events don't give you a clean batter name. Batter and pitcher come out as IDs, which means not only would you have to make that expensive iterative call, you'd need to make some other call to get player name data from the ID on top of that. It just doesn't work real well. Furthermore, as you can see from the 'des' attributes above, the events aren't even all at bats, as there are also "actions," such as the game status changing, mound visits, etc. It looks like it could get ugly fast.
Going through statsapi
and the linescore there seems easier, though still not "easy," per se. Nonetheless, linescore produces a lot of useful stuff. This is a snapshot from the Orioles/Marlins game going on as I type, with the Fish leading the Birds 3-1 in the top of the 3rd and Austin Hays having just ended the inning against Bruce Zimmerman:
statsapi.get('game_linescore', {'gamePk': 634476})
{
u"innings": [
{
u"home": {u"leftOnBase": 2, u"runs": 0, u"errors": 0, u"hits": 1},
u"away": {u"leftOnBase": 0, u"runs": 0, u"errors": 0, u"hits": 0},
u"num": 1,
u"ordinalNum": u"1st",
},
{
u"home": {u"leftOnBase": 1, u"runs": 0, u"errors": 0, u"hits": 1},
u"away": {u"leftOnBase": 1, u"runs": 0, u"errors": 0, u"hits": 1},
u"num": 2,
u"ordinalNum": u"2nd",
},
{
u"home": {u"leftOnBase": 0, u"hits": 0, u"errors": 0},
u"away": {u"leftOnBase": 0, u"runs": 0, u"errors": 0, u"hits": 1},
u"num": 3,
u"ordinalNum": u"3rd",
},
],
u"balls": 2,
u"currentInning": 3,
u"inningHalf": u"Top",
u"inningState": u"Top",
u"currentInningOrdinal": u"3rd",
u"scheduledInnings": 9,
u"strikes": 2,
u"teams": {
u"home": {u"leftOnBase": 3, u"runs": 0, u"errors": 0, u"hits": 2},
u"away": {u"leftOnBase": 1, u"runs": 0, u"errors": 0, u"hits": 2},
},
u"defense": {
u"onDeck": {
u"fullName": u"Miguel Rojas",
u"link": u"/api/v1/people/500743",
u"id": 500743,
},
u"catcher": {
u"fullName": u"Sandy Leon",
u"link": u"/api/v1/people/506702",
u"id": 506702,
},
u"center": {
u"fullName": u"Lewis Brinson",
u"link": u"/api/v1/people/621446",
u"id": 621446,
},
u"third": {
u"fullName": u"Jon Berti",
u"link": u"/api/v1/people/542932",
u"id": 542932,
},
u"shortstop": {
u"fullName": u"Miguel Rojas",
u"link": u"/api/v1/people/500743",
u"id": 500743,
},
u"pitcher": {
u"fullName": u"Trevor Rogers",
u"link": u"/api/v1/people/669432",
u"id": 669432,
},
u"second": {
u"fullName": u"Jazz Chisholm Jr.",
u"link": u"/api/v1/people/665862",
u"id": 665862,
},
u"right": {
u"fullName": u"Adam Duvall",
u"link": u"/api/v1/people/594807",
u"id": 594807,
},
u"batter": {
u"fullName": u"Jazz Chisholm Jr.",
u"link": u"/api/v1/people/665862",
u"id": 665862,
},
u"inHole": {
u"fullName": u"Jesus Aguilar",
u"link": u"/api/v1/people/542583",
u"id": 542583,
},
u"team": {u"link": u"/api/v1/teams/146", u"id": 146, u"name": u"Miami Marlins"},
u"left": {
u"fullName": u"Corey Dickerson",
u"link": u"/api/v1/people/572816",
u"id": 572816,
},
u"battingOrder": 10,
u"first": {
u"fullName": u"Jesus Aguilar",
u"link": u"/api/v1/people/542583",
u"id": 542583,
},
},
u"outs": 3,
u"offense": {
u"onDeck": {
u"fullName": u"Trey Mancini",
u"link": u"/api/v1/people/641820",
u"id": 641820,
},
u"battingOrder": 1,
u"pitcher": {
u"fullName": u"Bruce Zimmermann",
u"link": u"/api/v1/people/669145",
u"id": 669145,
},
u"batter": {
u"fullName": u"Austin Hays",
u"link": u"/api/v1/people/669720",
u"id": 669720,
},
u"inHole": {
u"fullName": u"Ryan Mountcastle",
u"link": u"/api/v1/people/663624",
u"id": 663624,
},
u"team": {
u"link": u"/api/v1/teams/110",
u"id": 110,
u"name": u"Baltimore Orioles",
},
},
u"isTopInning": True,
u"copyright": u"Copyright 2021 MLB Advanced Media, L.P. Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt",
}
from mlb-led-scoreboard.
@pfightingpolish thanks for everything! i installed MLB-StatsAPI (sudo install MLB-StatsAPI) and it installed, but when I run a .py with import statsapi, it says the module cannot be found. any reason why?
from mlb-led-scoreboard.
If you install under sudo
you will need to run your script under sudo
.
Hopefully also you ran sudo pip install MLB-StatsAPI
.
from mlb-led-scoreboard.
sorry, i did use sudo pip to install. Well i added the atbat.py and ran just that and it gave me that error. I then added stuff in the scoreboard.py and same error. Do i have to run the whole service for it to work?
from mlb-led-scoreboard.
Related Issues (20)
- If emoji is present in News Article Title, then python script crashes due to a ZeroDivisionError HOT 12
- Scoreboard >= v6.3.0 causes games to get "stuck" without rotating HOT 10
- Add result of previous play to game screen
- Add support for 96 x 48 matrix HOT 1
- Issue when trying to set Dbacks as preferred team HOT 9
- Delayed game still showed start time HOT 1
- Units of measurement not working HOT 2
- Scoreboard not starting with one team config and offday HOT 2
- Turning off scrolling text HOT 4
- Option to display team record on game screen HOT 1
- Trouble starting up when plugged in HOT 7
- Top Half of screen is green HOT 2
- Verify software compatibility with Raspberry Pi 5 HOT 1
- Error when installing HOT 3
- Create Image HOT 1
- Just set up the hardware and I have an issue starting the software HOT 6
- no module stats API HOT 6
- PIL failed to load HOT 1
- Update installer to disable soundcard HOT 3
- new install on Bookworm, No module named 'statsapi', then No module named 'rgbmatrix' HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from mlb-led-scoreboard.