hinodexrt / xrtpy Goto Github PK
View Code? Open in Web Editor NEWHome Page: https://xrtpy.readthedocs.io
License: BSD 2-Clause "Simplified" License
Home Page: https://xrtpy.readthedocs.io
License: BSD 2-Clause "Simplified" License
The Temperature_Response_Fundamental.temperature_response()
returns all NaNs. This can be seen in the example gallery entry in the docs: https://xrtpy.readthedocs.io/en/latest/notebooks/computing_functions/temperature_response.html. I've tried a number of different dates and this always seems to return NaN independent of the date.
Some preliminary testing on my end shows that the NaN entries start appearing in Temperature_Response_Fundamental.effective_area()
method which returns all NaNs.
This wasn't caught by the tests because the temperature response tests are being skipped. See #98
On this page, it looks like the titles of the example notebooks aren't showing up correctly. This might be because of the way the titles are included in the notebooks, but we probably need to check this to make sure.
...since Astropy and NumPy are dropping support, following the schedule in NumPy Enhancement Proposal 29.
Probably the best way to approach this would be...
test_channel.py
Channel(...).transmission
against the numpy array from the previous step using numpy.allclose
(e.g., assert numpy.allclose(..., ...)
)When cloning the repository, I noticed that the total size of the repository is now over 150 MB. My understanding is that this is also preventing releases due to the upper limit of package sizes on PyPI being 60 MB.
To remedy this, I would suggest making use of the data manager functionality in sunpy
. Below, I've listed a few files that I think this could easily apply to:
data/XRT20170324_151721.0.PSF560.fits
in xrt_deconvolve
data/XRT20170324_161721.0.PSF1000.fits
in xrt_deconvolve
data/leak_fits
in xrt_remove_lightleak
I'm still looking through the repository and will continue to add candidates to this list.
There are several functions that are prepended with xrt_
. I assume this is to be consistent with what they are called in IDL. These include:
xrt_remove_lightleak
xrt_deconvolve
xrt_teem
I would suggest removing the xrt_
that is prepended to each filename. In IDL, such naming conventions are needed because there is no concept of a namespace. However, all of these functions live within the xrtpy
package and thus it is not necessary to explicitly say in the function name that these are associated with XRT/xrtpy
.
Additionally, I find the name "teem" very confusing/ambiguous. I realize again that this is to be consistent with the function name in IDL, but this name will not be meaningful to new users/those who have never used the IDL tools. I would suggest a more descriptive name for this function, something along the lines of get_temperature_from_filter_ratio
.
I'd suggest the Contributor Covenant version 2.
This issue is a suggested API change to support time inputs as astropy.time.Time
objects. Astropy time is now the standard way of expressing time in the sunpy ecosystem so many users may already have their times in Astropy time objects.
One way to go could be to simply add astropy.time.Time
as a supported input. Alternatively astropy.time.Time
could be the only accepted format and your docs could show examples like
>>> from sunpy.time import parse_time
>>> result = func(parse_time("2000-01-01T00:00"))
The latter case might reduce your maintenance burden but would be a breaking change.
While working on #33 in the last few days, we ran into a persistent error in effective_area.py
and temperature_response.py
. As far as I can tell, the problem is not with xrtpy (since otherwise the tests are passing) but rather a problem with test collection in those two files. The traceback is below, though I found it pretty unhelpful.
In #33 we implemented a workaround by telling pytest to ignore those two files. Later on, we should resolve this issue or figure out a better workaround so that doctests can be run in these two files.
______________ ERROR collecting xrtpy/response/effective_area.py _______________
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/doctest.py:940: in find
self._find(tests, obj, name, module, source_lines, globs, {})
.nox/tests-3-10/lib/python3.10/site-packages/_pytest/doctest.py:533: in _find
super()._find( # type:ignore[misc]
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/doctest.py:1011: in _find
if ((self._is_routine(val) or inspect.isclass(val)) and
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/doctest.py:982: in _is_routine
maybe_routine = inspect.unwrap(maybe_routine)
.nox/tests-3-10/lib/python3.10/site-packages/_pytest/doctest.py:475: in _mock_aware_unwrap
return real_unwrap(func, stop=_is_mocked)
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/inspect.py:639: in unwrap
while _is_wrapper(func):
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/inspect.py:633: in _is_wrapper
return hasattr(f, '__wrapped__') and not stop(f)
.nox/tests-3-10/lib/python3.10/site-packages/scipy/io/_idl.py:651: in __getitem__
return super().__getitem__(name.lower())
E KeyError: '__wrapped__'
___________ ERROR collecting xrtpy/response/temperature_response.py ____________
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/doctest.py:940: in find
self._find(tests, obj, name, module, source_lines, globs, {})
.nox/tests-3-10/lib/python3.10/site-packages/_pytest/doctest.py:533: in _find
super()._find( # type:ignore[misc]
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/doctest.py:1011: in _find
if ((self._is_routine(val) or inspect.isclass(val)) and
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/doctest.py:982: in _is_routine
maybe_routine = inspect.unwrap(maybe_routine)
.nox/tests-3-10/lib/python3.10/site-packages/_pytest/doctest.py:475: in _mock_aware_unwrap
return real_unwrap(func, stop=_is_mocked)
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/inspect.py:639: in unwrap
while _is_wrapper(func):
/opt/hostedtoolcache/Python/3.10.4/x64/lib/python3.10/inspect.py:633: in _is_wrapper
return hasattr(f, '__wrapped__') and not stop(f)
.nox/tests-3-10/lib/python3.10/site-packages/scipy/io/_idl.py:651: in __getitem__
return super().__getitem__(name.lower())
E KeyError: '__wrapped__'
I removed the build-n-publish
package from the test configuration in #54 because it was failing on a command to check out the package without giving any helpful error messages. We'll need to add back in a GitHub Action that tests building the package and publishing it to TestPyPI.
If we follow the deprecation schedule in NumPy Enhancement Proposal 29, we'll be able to drop support for Python 3.8 starting with releases made after April 2023. This would probably be a good idea, since upstream packages like Astropy, SunPy, and NumPy will all be dropping Python 3.8 support around that time. When we drop support for Python 3.8, we can use pyupgrade
to automagically update a lot of the syntax from 3.8+ to 3.9+.
This would involve adding the appropriate substitutions in docs/_substitutions.rst
, and then applying them elsewhere in the narrative documentation and docstrings.
The contamination data files are updated monthly, and we ran into an error for 0.1.0 where we forgot to update it (#74).
One possibility to address this would be to create a GitHub Action that looks for an update to the contamination data file, creates a new branch if there has been an update,
main
We'd also want to do this for, e.g., the v0.2.x
branch, which we'll create for the next release. In that case, we automate a monthly patch release when the file is updated.
A big caveat for this is if getting the contamination data file requires using IDL. I don't think we'd be able to do a GitHub Action in that case, but we might be able to write a script so that we could do this with a single command.
Ideally, in the longer term, we'd create a way to separate the data files from the code repo, and then have the code check and download updated data, if necessary. Given constraints on the time we'd have to implement this, however, a GitHub Action like this would probably suffice.
CodeMeta is an effort to standardize how metadata for software projects is organized. Eventually, this should make it easier to upload software packages to Zenodo, for example. We should be able to create a codemeta.json
file from the CodeMeta generator they have on their website. This should be done soon before xrtpy's version 0.1.0 release since the CodeMeta standard might change, and because we'll have a better idea about what the metadata will actually be later on. The codemeta.json
file would then need to be updated before every official release.
Having a release guide is really helpful for doing releases, since there can be a lot of steps and it's easy to forget some of them. We probably want to write it at about the same time as doing the 0.1.0 release, and have an initial draft for the 0.2.0 release. Then after that we can revise it for every subsequent release. A lot of this could probably be adapted from the release guide for PlasmaPy or perhaps other packages.
In setup.cfg there is the line:
looponfailroots =
xrtpy
That seems to cause a deprecation warning by current versions of pytest:
DeprecationWarning: The --looponfail command line argument and looponfailroots config variable are deprecated.
The loop-on-fail feature will be removed in pytest-xdist 4.0.
config.issue_config_time_warning(warning, 2)
Hi XRTpy team. I'm excited to see that this package is being developed. When trying it out myself I encountered a ValueError
error when executing the temperature_response
function for the Be-thick filter. Below is the code I wrote that raised the error as well as the traceback.
>>> from xrtpy.response import TemperatureResponseFundamental
>>> be_thick = TemperatureResponseFundamental("Be-thick", "2021-05-07")
>>> be_thick.temperature_response()
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Input In [26], in <cell line: 1>()
----> 1 be_thick_temp_resp = be_thick.temperature_response()
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/site-packages/astropy/units/decorators.py:304, in QuantityInput.__call__.<locals>.wrapper(*func_args, **func_kwargs)
302 # Call the original function with any equivalencies in force.
303 with add_enabled_equivalencies(self.equivalencies):
--> 304 return_ = wrapped_function(*func_args, **func_kwargs)
306 # Return
307 ra = wrapped_signature.return_annotation
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/site-packages/xrtpy/response/temperature_response.py:183, in TemperatureResponseFundamental.temperature_response(self)
180 @u.quantity_input
181 def temperature_response(self) -> u.DN * u.cm**5 / (u.s * u.pix):
182 """Apply gain value to the Temperature Response in units of DN cm\ :sup:`5` s\ :sup:`-1` pix\ :sup:`-1`."""
--> 183 return self.integration() / self.ccd_gain_right
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/site-packages/astropy/units/decorators.py:304, in QuantityInput.__call__.<locals>.wrapper(*func_args, **func_kwargs)
302 # Call the original function with any equivalencies in force.
303 with add_enabled_equivalencies(self.equivalencies):
--> 304 return_ = wrapped_function(*func_args, **func_kwargs)
306 # Return
307 ra = wrapped_signature.return_annotation
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/site-packages/xrtpy/response/temperature_response.py:162, in TemperatureResponseFundamental.integration(self)
160 constants = (_c_Å_per_s * _h_eV_s / self.channel_wavelength).value
161 factors = (self.solid_angle_per_pixel / self.ev_per_electron).value
--> 162 effective_area = (self.effective_area()).value
164 temp_resp_w_u_c = [
165 integrate.simpson(
166 self.spectra()[i] * effective_area * constants * factors,
(...)
169 for i in range(61)
170 ]
172 return temp_resp_w_u_c * (u.electron * u.cm**5 * (1 / u.s) * (1 / u.pix))
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/site-packages/astropy/units/decorators.py:304, in QuantityInput.__call__.<locals>.wrapper(*func_args, **func_kwargs)
302 # Call the original function with any equivalencies in force.
303 with add_enabled_equivalencies(self.equivalencies):
--> 304 return_ = wrapped_function(*func_args, **func_kwargs)
306 # Return
307 ra = wrapped_signature.return_annotation
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/site-packages/xrtpy/response/temperature_response.py:154, in TemperatureResponseFundamental.effective_area(self)
152 @u.quantity_input
153 def effective_area(self) -> u.cm**2:
--> 154 return effective_area(self.name, self.observation_date)
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/site-packages/xrtpy/response/effective_area.py:449, in effective_area(filter_name, observation_date)
447 def effective_area(filter_name, observation_date):
448 EAP = EffectiveAreaFundamental(filter_name, observation_date)
--> 449 return EAP.effective_area()
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/site-packages/astropy/units/decorators.py:304, in QuantityInput.__call__.<locals>.wrapper(*func_args, **func_kwargs)
302 # Call the original function with any equivalencies in force.
303 with add_enabled_equivalencies(self.equivalencies):
--> 304 return_ = wrapped_function(*func_args, **func_kwargs)
306 # Return
307 ra = wrapped_signature.return_annotation
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/site-packages/xrtpy/response/effective_area.py:443, in EffectiveAreaFundamental.effective_area(self)
436 @u.quantity_input
437 def effective_area(self) -> u.cm**2:
438 """Calculation of the Effective Area."""
439 return (
440 self.channel_geometry_aperture_area
441 * self.channel_transmission
442 * self.interpolated_CCD_contamination_transmission
--> 443 * self.interpolated_filter_contamination_transmission
444 )
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/site-packages/xrtpy/response/effective_area.py:432, in EffectiveAreaFundamental.interpolated_filter_contamination_transmission(self)
428 @property
429 def interpolated_filter_contamination_transmission(self):
430 """Interpolate filter contam transmission to the wavelength."""
431 Filter_contam_transmission = interpolate.interp1d(
--> 432 self.n_DEHP_wavelength, self.filter_contamination_transmission
433 )
434 return Filter_contam_transmission(self.channel_wavelength)
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/functools.py:981, in cached_property.__get__(self, instance, owner)
979 val = cache.get(self.attrname, _NOT_FOUND)
980 if val is _NOT_FOUND:
--> 981 val = self.func(instance)
982 try:
983 cache[self.attrname] = val
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/site-packages/xrtpy/response/effective_area.py:400, in EffectiveAreaFundamental.filter_contamination_transmission(self)
397 i_i = complex(0, 1) # Define complex number
399 # Define transfer matrix
--> 400 M = [
401 [
402 [
403 np.cos(self.filterwheel_angular_wavenumber[i]),
404 (-i_i * np.sin(self.filterwheel_angular_wavenumber[i])) / index[i],
405 ],
406 [
407 -i_i * np.sin(self.filterwheel_angular_wavenumber[i]) * index[i],
408 np.cos(self.filterwheel_angular_wavenumber[i]),
409 ],
410 ]
411 for i in range(4000)
412 ]
414 transmittance = [
415 2
416 * n_o
(...)
423 for i in range(4000)
424 ]
426 return [abs(transmittance[i] ** 2) for i in range(4000)]
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/site-packages/xrtpy/response/effective_area.py:403, in <listcomp>(.0)
397 i_i = complex(0, 1) # Define complex number
399 # Define transfer matrix
400 M = [
401 [
402 [
--> 403 np.cos(self.filterwheel_angular_wavenumber[i]),
404 (-i_i * np.sin(self.filterwheel_angular_wavenumber[i])) / index[i],
405 ],
406 [
407 -i_i * np.sin(self.filterwheel_angular_wavenumber[i]) * index[i],
408 np.cos(self.filterwheel_angular_wavenumber[i]),
409 ],
410 ]
411 for i in range(4000)
412 ]
414 transmittance = [
415 2
416 * n_o
(...)
423 for i in range(4000)
424 ]
426 return [abs(transmittance[i] ** 2) for i in range(4000)]
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/functools.py:981, in cached_property.__get__(self, instance, owner)
979 val = cache.get(self.attrname, _NOT_FOUND)
980 if val is _NOT_FOUND:
--> 981 val = self.func(instance)
982 try:
983 cache[self.attrname] = val
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/site-packages/xrtpy/response/effective_area.py:321, in EffectiveAreaFundamental.filterwheel_angular_wavenumber(self)
313 angular_wavenumber = np.array(
314 [
315 (2.0 * math.pi * index[i] * cos_a) / self.n_DEHP_wavelength[i]
316 for i in range(4000)
317 ]
318 )
320 # Multiply by thickness
--> 321 angular_wavenumber_thickness = angular_wavenumber * self.contamination_on_filter
323 real_angular_wavenumber = angular_wavenumber_thickness.real
324 imaginary_angular_wavenumber = angular_wavenumber_thickness.imag
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/site-packages/xrtpy/response/effective_area.py:193, in EffectiveAreaFundamental.contamination_on_filter(self)
187 """
188 Thickness of the contamination layer on a filter."""
190 interpolater = scipy.interpolate.interp1d(
191 self.filter_data_dates_to_seconds, self.filter_data, kind="linear"
192 )
--> 193 return interpolater(self.filter_observation_date_to_seconds)
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/site-packages/scipy/interpolate/_polyint.py:78, in _Interpolator1D.__call__(self, x)
57 """
58 Evaluate the interpolant
59
(...)
75
76 """
77 x, x_shape = self._prepare_x(x)
---> 78 y = self._evaluate(x)
79 return self._finish_y(y, x_shape)
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/site-packages/scipy/interpolate/_interpolate.py:695, in interp1d._evaluate(self, x_new)
693 y_new = self._call(self, x_new)
694 if not self._extrapolate:
--> 695 below_bounds, above_bounds = self._check_bounds(x_new)
696 if len(y_new) > 0:
697 # Note fill_value must be broadcast up to the proper size
698 # and flattened to work here
699 y_new[below_bounds] = self._fill_value_below
File ~/miniconda3/envs/xray-stereoscopy/lib/python3.10/site-packages/scipy/interpolate/_interpolate.py:727, in interp1d._check_bounds(self, x_new)
724 raise ValueError("A value in x_new is below the interpolation "
725 "range.")
726 if self.bounds_error and above_bounds.any():
--> 727 raise ValueError("A value in x_new is above the interpolation "
728 "range.")
730 # !! Should we emit a warning if some values are out of bounds?
731 # !! matlab does not.
732 return below_bounds, above_bounds
ValueError: A value in x_new is above the interpolation range.
xrtpy: 0.1.0
astropy: 5.0.1
scipy: 1.8.0
One thing that came up as a side issue of #89 is that the reStructuredText links to Python objects in other packages (i.e., ~sunpy.map.sources.hinode.XRTMap
in the docstring for xrt_teem
) isn't working. This may be an issue with how intersphinx is set up, or perhaps an incompatibility between different packages used for the docs (e.g., something like sphinx_rtd_theme
vs. a new release of sphinx
). I took a quick look at what's in the documentation configuration file for Sphinx (docs/conf.py
) and didn't see any issues, so I don't know what's causing it.
I would like to suggest an enhancement to provide better print/return information for users utilizing the abundance feature. Currently, when users use the abundance option, they do not receive any feedback regarding which abundance model is being used in the calculations.
Proposed Solution:
I propose implementing a return statement that informs the user about the specific abundance model being utilized in the calculations. This information should be displayed whenever the user employs the abundance feature within the XRTpy package. Furthermore, adding more details to the doc string within the function.
This enhancement will significantly improve the user experience and help users have a clear understanding of the abundance models used in their calculations, facilitating better interpretation and utilization of the results.
This should be at changelog/README.rst
in PlasmaPy's repo.
PR #40 creates docs/install.rst
which includes installation instructions for xrtpy
which were copied over from PlasmaPy's repository. I put an admonition near the top that states that the instructions are written in preparation for the 0.1.0 release, and thus are not valid yet. These will need to be updated before the 0.1.0 release.
...since it was just released on October 4.
Related to #74 and #77, it would be helpful to add a cron test that takes a date approximately 1.5–2 months prior to the date the test is being run, and makes sure that we're able to get a temperature response. This would serve as a reminder for when we need to update the contamination data files.
This would only be a temporary hack, as we need to figure out a longer term solution for making sure that the contamination data files are kept up-to-date.
Currently the value returned by an XRTMap object created via
import sunpy.map
xrtmap = sunpy.map.Map(filepath)
gives the filter combination as an attribute of xrtmap:
xrtmap.measurement = 'Be thin-Open'
However the format is not compatible with that accepted by XRTpy.response.temperature_response.TemperatureResponseFundamental
e.g. 'Be-thin' or 'Be_thin'
For the sake of interoperability these should be made compatible (not saying which should change).
The bug raised in #74 occurred because a date was chosen after the end of the contamination data file. It would be helpful to have an error message for when a user picks a date that is after the end of the data contained in the contamination data file, like "No contamination data is presently available for ." This could be done in a try
/except
block.
ReviewNB is a tool that we can add to make it easier to review Jupyter notebooks on GitHub.
...using these instructions on adding packages to conda-forge.
After this we'll be able to install xrtpy with commands like
conda install xrtpy -c conda-forge
The tests that do code coverage are not finding the .genx
file, and are giving a FileNotFoundError
. There might be a problem in tox.ini
or MANIFEST.in
but I'm not sure where yet.
The test for the temperature response function seems to be unintentionally skipped. I am finding this locally as well as on the CI: https://github.com/HinodeXRT/xrtpy/actions/runs/3681186541/jobs/6227651229#step:5:40
I think this might need an api_static
page.
...this would be something like this page from PlasmaPy.
This would involve setting up a GitHub Action that would do a test of the build for Read the Docs, though a backup/temporary possibility would be setting this up with CircleCI. The workflow would probably use tox
to run the actual documentation build test.
Instead of constructing paths manually, as is done here,
xrtpy/xrtpy/response/effective_area.py
Lines 41 to 43 in d3e994c
it would be better to use the get_pkg_data_filename
function to do this (see #99).
Unfortunately, this is complicated by the fact that these bits of code are currently run at import time which results in an import error because the xrtpy.response
namespace cannot be resolved immediately at import time.
Instead, I would recommend that this code to read these files be moved into the objects themselves, unless doing so would create a lot of overhead in reading these files.
I noticed that 'Ti_poly' is not a recognized filter name. I could see where you might want consistency in the sense of using only dashes in filter names, however if someone wants to use the filter name directly from the header, then they'll get an error. Maybe the code that checks the filter names could try, if there is no match, converting any '_' s to '-' s.
This could be a sentence or two that describes what xrtpy
will be, and perhaps also says what Hinode/XRT is.
There are several places throughout the package where print
statements are used to inform the user about something that is happening when a "verbose" flag is set. Instead of print
statements, it would be better to use a logger throughout the package. This allows a user to more easily control how much is printed to the screen.
I think what we did in PlasmaPy was set the version of jinja2 to be less than the most recent version.
util
subpackages are typically used for utility/misc functions are useful across subpackages. Putting xrt_deconvolve
and xrt_remove_lightleak
in util
makes them harder for users to find. I would suggest putting them in their own subpackage (name TBD) to make them more visible to users.
XRTpy does not currently calculate the Temperature Response & Effective Area for two-filter combo.
#40 adds the directory docs/response
as a place to put the narrative documentation for xrtpy.response
. Before the 0.1.0 release, we should add an initial draft of the narrative docs for this page into this directory. The initial draft doesn't need to go into a huge amount of detail, but should be enough to describe how to calculate the effective area and temperature response function. We could expand this for the 0.2.0 release.
xrtpy uses data that is in SolarSoft in .geny
format, and some of the .geny
files to be implemented are time-dependent and updated with a quasiperiodicity of ∼3 weeks. We had trouble working with .geny
files directly. We decided that converting the .geny
file to a .genx
file within SolarSoft, uploading the .genx
file to xrtpy, and then using SunPy's read_genx
function was the easiest way we could figure out going about this. We should include how to do this in xrtpy's developer documentation. Related to #13.
Right now temperature response functions read spectra out of a gene file. We would like to expand the functionality so that a user can optionally supply their own spectra.
This could possibly be the same action as #55.
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.