aholkner / euclid Goto Github PK
View Code? Open in Web Editor NEWPython library for graphics-related maths
License: BSD 3-Clause "New" or "Revised" License
Python library for graphics-related maths
License: BSD 3-Clause "New" or "Revised" License
This should be more efficient.
diff --git a/euclid.py b/euclid.py
index 66a1d6e..1a1f97d 100644
--- a/euclid.py
+++ b/euclid.py
@@ -222,8 +222,7 @@ class Vector2:
__pos__ = __copy__
def __abs__(self):
- return math.sqrt(self.x ** 2 + \
- self.y ** 2)
+ return math.hypot(self.x, self.y)
magnitude = __abs__
@@ -474,9 +473,7 @@ class Vector3:
__pos__ = __copy__
def __abs__(self):
- return math.sqrt(self.x ** 2 + \
- self.y ** 2 + \
- self.z ** 2)
+ return math.sqrt(self.x * self.x, self.y * self.y, self.z * self.z)
magnitude = __abs__
@@ -1286,10 +1283,7 @@ class Quaternion:
return self
def __abs__(self):
- return math.sqrt(self.w ** 2 + \
- self.x ** 2 + \
- self.y ** 2 + \
- self.z ** 2)
+ return math.hypot(self.w * self.w, self.x * self.x, self.y * self.y, self.z * self.z)
magnitude = __abs__
Actually math.hypot
can be used for 3 or 4 components as well, but only in Python 3.8 or later.
Code looks a bit cleaner, don’t you think:
diff --git a/euclid.py b/euclid.py
index 21da55f..52a32b4 100644
--- a/euclid.py
+++ b/euclid.py
@@ -247,6 +247,7 @@ class Vector2:
assert isinstance(other, Vector2)
return self.x * other.x + \
self.y * other.y
+ __matmul__ = dot
def cross(self):
return Vector2(self.y, -self.x)
@@ -260,12 +261,12 @@ class Vector2:
def angle(self, other):
"""Return the angle to the vector other"""
- return math.acos(self.dot(other) / (self.magnitude()*other.magnitude()))
+ return math.acos(self @ other / (self.magnitude()*other.magnitude()))
def project(self, other):
"""Return one vector projected on the vector other"""
n = other.normalized()
- return self.dot(n)*n
+ return (self @ n)*n
class Vector3:
__slots__ = ['x', 'y', 'z']
@@ -502,6 +503,7 @@ class Vector3:
return self.x * other.x + \
self.y * other.y + \
self.z * other.z
+ __matmul__ = dot
def cross(self, other):
assert isinstance(other, Vector3)
@@ -537,12 +539,12 @@ class Vector3:
def angle(self, other):
"""Return the angle to the vector other"""
- return math.acos(self.dot(other) / (self.magnitude()*other.magnitude()))
+ return math.acos(self @ other / (self.magnitude()*other.magnitude()))
def project(self, other):
"""Return one vector projected on the vector other"""
n = other.normalized()
- return self.dot(n)*n
+ return (self @ n)*n
# a b c
# e f g
@@ -1576,7 +1578,7 @@ def _intersect_line2_circle(L, C):
L.v.y * (L.p.y - C.c.y))
c = C.c.magnitude_squared() + \
L.p.magnitude_squared() - \
- 2 * C.c.dot(L.p) - \
+ 2 * C.c @ L.p - \
C.r ** 2
det = b ** 2 - 4 * a * c
if det < 0:
@@ -1861,15 +1863,15 @@ def _connect_point3_sphere(P, S):
def _connect_point3_plane(p, plane):
n = plane.n.normalized()
- d = p.dot(plane.n) - plane.k
+ d = p @ plane.n - plane.k
return LineSegment3(p, Point3(p.x - n.x * d, p.y - n.y * d, p.z - n.z * d))
def _connect_line3_line3(A, B):
assert A.v and B.v
p13 = A.p - B.p
- d1343 = p13.dot(B.v)
- d4321 = B.v.dot(A.v)
- d1321 = p13.dot(A.v)
+ d1343 = p13 @ B.v
+ d4321 = B.v @ A.v
+ d1321 = p13 @ A.v
d4343 = B.v.magnitude_squared()
denom = A.v.magnitude_squared() * d4343 - d4321 ** 2
if denom == 0:
@@ -1894,11 +1896,11 @@ def _connect_line3_line3(A, B):
B.p.z + ub * B.v.z))
def _connect_line3_plane(L, P):
- d = P.n.dot(L.v)
+ d = P.n @ L.v
if not d:
# Parallel, choose an endpoint
return _connect_point3_plane(L.p, P)
- u = (P.k - P.n.dot(L.p)) / d
+ u = (P.k - P.n @ L.p) / d
if not L._u_in(u):
# intersects out of range, choose nearest endpoint
u = max(min(u, 1.0), 0.0)
@@ -1972,7 +1974,7 @@ def _intersect_line3_sphere(L, S):
L.v.z * (L.p.z - S.c.z))
c = S.c.magnitude_squared() + \
L.p.magnitude_squared() - \
- 2 * S.c.dot(L.p) - \
+ 2 * S.c @ L.p - \
S.r ** 2
det = b ** 2 - 4 * a * c
if det < 0:
@@ -1992,11 +1994,11 @@ def _intersect_line3_sphere(L, S):
L.p.z + u2 * L.v.z))
def _intersect_line3_plane(L, P):
- d = P.n.dot(L.v)
+ d = P.n @ L.v
if not d:
# Parallel
return None
- u = (P.k - P.n.dot(L.p)) / d
+ u = (P.k - P.n @ L.p) / d
if not L._u_in(u):
return None
return Point3(L.p.x + u * L.v.x,
@@ -2006,7 +2008,7 @@ def _intersect_line3_plane(L, P):
def _intersect_plane_plane(A, B):
n1_m = A.n.magnitude_squared()
n2_m = B.n.magnitude_squared()
- n1d2 = A.n.dot(B.n)
+ n1d2 = A.n @ B.n
det = n1_m * n2_m - n1d2 ** 2
if det == 0:
# Parallel
@@ -2220,11 +2222,11 @@ class Plane:
isinstance(args[2], Point3)
self.n = (args[1] - args[0]).cross(args[2] - args[0])
self.n.normalize()
- self.k = self.n.dot(args[0])
+ self.k = self.n @ args[0]
elif len(args) == 2:
if isinstance(args[0], Point3) and isinstance(args[1], Vector3):
self.n = args[1].normalized()
- self.k = self.n.dot(args[0])
+ self.k = self.n @ args[0]
elif isinstance(args[0], Vector3) and isinstance(args[1], float):
self.n = args[0].normalized()
self.k = args[1]
@@ -2258,7 +2260,7 @@ class Plane:
def _apply_transform(self, t):
p = t * self._get_point()
self.n = t * self.n
- self.k = self.n.dot(p)
+ self.k = self.n @ p
def intersect(self, other):
return other._intersect_plane(self)
Makes the code slightly simpler and more general:
diff --git a/euclid.py b/euclid.py
index cd75766..21da55f 100644
--- a/euclid.py
+++ b/euclid.py
@@ -40,7 +40,8 @@ __revision__ = '$Revision: 37 $'
import math
import operator
-import types
+from numbers import \
+ Real
# If True, allows components of Vector2 and Vector3 to be set via swizzling;
# e.g. v.xyz = (1, 2, 3). This is much, much slower than the more verbose
@@ -168,48 +169,48 @@ class Vector2:
other.y - self[1])
def __mul__(self, other):
- assert type(other) in (int, float)
+ assert isinstance(other, Real)
return Vector2(self.x * other,
self.y * other)
__rmul__ = __mul__
def __imul__(self, other):
- assert type(other) in (int, float)
+ assert isinstance(other, Real)
self.x *= other
self.y *= other
return self
def __div__(self, other):
- assert type(other) in (int, float)
+ assert isinstance(other, Real)
return Vector2(operator.div(self.x, other),
operator.div(self.y, other))
def __rdiv__(self, other):
- assert type(other) in (int, float)
+ assert isinstance(other, Real)
return Vector2(operator.div(other, self.x),
operator.div(other, self.y))
def __floordiv__(self, other):
- assert type(other) in (int, float)
+ assert isinstance(other, Real)
return Vector2(operator.floordiv(self.x, other),
operator.floordiv(self.y, other))
def __rfloordiv__(self, other):
- assert type(other) in (int, float)
+ assert isinstance(other, Real)
return Vector2(operator.floordiv(other, self.x),
operator.floordiv(other, self.y))
def __truediv__(self, other):
- assert type(other) in (int, float)
+ assert isinstance(other, Real)
return Vector2(operator.truediv(self.x, other),
operator.truediv(self.y, other))
def __rtruediv__(self, other):
- assert type(other) in (int, float)
+ assert isinstance(other, Real)
return Vector2(operator.truediv(other, self.x),
operator.truediv(other, self.y))
@@ -410,7 +411,7 @@ class Vector3:
self.y * other.y,
self.z * other.z)
else:
- assert type(other) in (int, float)
+ assert isinstance(other, Real)
return Vector3(self.x * other,
self.y * other,
self.z * other)
@@ -418,47 +419,47 @@ class Vector3:
__rmul__ = __mul__
def __imul__(self, other):
- assert type(other) in (int, float)
+ assert isinstance(other, Real)
self.x *= other
self.y *= other
self.z *= other
return self
def __div__(self, other):
- assert type(other) in (int, float)
+ assert isinstance(other, Real)
return Vector3(operator.div(self.x, other),
operator.div(self.y, other),
operator.div(self.z, other))
def __rdiv__(self, other):
- assert type(other) in (int, float)
+ assert isinstance(other, Real)
return Vector3(operator.div(other, self.x),
operator.div(other, self.y),
operator.div(other, self.z))
def __floordiv__(self, other):
- assert type(other) in (int, float)
+ assert isinstance(other, Real)
return Vector3(operator.floordiv(self.x, other),
operator.floordiv(self.y, other),
operator.floordiv(self.z, other))
def __rfloordiv__(self, other):
- assert type(other) in (int, float)
+ assert isinstance(other, Real)
return Vector3(operator.floordiv(other, self.x),
operator.floordiv(other, self.y),
operator.floordiv(other, self.z))
def __truediv__(self, other):
- assert type(other) in (int, float)
+ assert isinstance(other, Real)
return Vector3(operator.truediv(self.x, other),
operator.truediv(self.y, other),
operator.truediv(self.z, other))
def __rtruediv__(self, other):
- assert type(other) in (int, float)
+ assert isinstance(other, Real)
return Vector3(operator.truediv(other, self.x),
operator.truediv(other, self.y),
operator.truediv(other, self.z))
@@ -1699,7 +1700,7 @@ class Line2(Geometry):
if len(args) == 3:
assert isinstance(args[0], Point2) and \
isinstance(args[1], Vector2) and \
- type(args[2]) == float
+ isinstance(args[2], float)
self.p = args[0].copy()
self.v = args[1] * args[2] / abs(args[1])
elif len(args) == 2:
@@ -1798,7 +1799,7 @@ class Circle(Geometry):
__slots__ = ['c', 'r']
def __init__(self, center, radius):
- assert isinstance(center, Vector2) and type(radius) == float
+ assert isinstance(center, Vector2) and isinstance(radius, float)
self.c = center.copy()
self.r = radius
@@ -2057,7 +2058,7 @@ class Line3:
if len(args) == 3:
assert isinstance(args[0], Point3) and \
isinstance(args[1], Vector3) and \
- type(args[2]) == float
+ isinstance(args[2], float)
self.p = args[0].copy()
self.v = args[1] * args[2] / abs(args[1])
elif len(args) == 2:
@@ -2164,7 +2165,7 @@ class Sphere:
__slots__ = ['c', 'r']
def __init__(self, center, radius):
- assert isinstance(center, Vector3) and type(radius) == float
+ assert isinstance(center, Vector3) and isinstance(radius, float)
self.c = center.copy()
self.r = radius
@@ -2224,7 +2225,7 @@ class Plane:
if isinstance(args[0], Point3) and isinstance(args[1], Vector3):
self.n = args[1].normalized()
self.k = self.n.dot(args[0])
- elif isinstance(args[0], Vector3) and type(args[1]) == float:
+ elif isinstance(args[0], Vector3) and isinstance(args[1], float):
self.n = args[0].normalized()
self.k = args[1]
else:
I think cases of isinstance(
val, float)
should be replaced with isinstance(
val, Real)
as well.
Should have posted this first, since some of my other patches might not apply cleanly without it. But nobody should be doing new development in Python 2 any more.
diff --git a/euclid.py b/euclid.py
index 7f09de8..c8b99d8 100644
--- a/euclid.py
+++ b/euclid.py
@@ -44,57 +44,12 @@ import math
import operator
import types
-# Some magic here. If _use_slots is True, the classes will derive from
-# object and will define a __slots__ class variable. If _use_slots is
-# False, classes will be old-style and will not define __slots__.
-#
-# _use_slots = True: Memory efficient, probably faster in future versions
-# of Python, "better".
-# _use_slots = False: Ordinary classes, much faster than slots in current
-# versions of Python (2.4 and 2.5).
-_use_slots = True
-
# If True, allows components of Vector2 and Vector3 to be set via swizzling;
# e.g. v.xyz = (1, 2, 3). This is much, much slower than the more verbose
# v.x = 1; v.y = 2; v.z = 3, and slows down ordinary element setting as
# well. Recommended setting is False.
_enable_swizzle_set = False
-# Requires class to derive from object.
-if _enable_swizzle_set:
- _use_slots = True
-
-# Implement _use_slots magic.
-class _EuclidMetaclass(type):
- def __new__(cls, name, bases, dct):
- if '__slots__' in dct:
- dct['__getstate__'] = cls._create_getstate(dct['__slots__'])
- dct['__setstate__'] = cls._create_setstate(dct['__slots__'])
- if _use_slots:
- return type.__new__(cls, name, bases + (object,), dct)
- else:
- if '__slots__' in dct:
- del dct['__slots__']
- return types.ClassType.__new__(types.ClassType, name, bases, dct)
-
- @classmethod
- def _create_getstate(cls, slots):
- def __getstate__(self):
- d = {}
- for slot in slots:
- d[slot] = getattr(self, slot)
- return d
- return __getstate__
-
- @classmethod
- def _create_setstate(cls, slots):
- def __setstate__(self, state):
- for name, value in state.items():
- setattr(self, name, value)
- return __setstate__
-
-__metaclass__ = _EuclidMetaclass
-
class Vector2:
__slots__ = ['x', 'y']
__hash__ = None
How about defining the swizzle methods this way. It should have less run-time overhead than the __setitem__
technique, at the cost of a proliferation of properties in the affected classes.
diff --git a/euclid.py b/euclid.py
index 5fdc285..824ce8a 100644
--- a/euclid.py
+++ b/euclid.py
@@ -43,12 +43,64 @@ import operator
from numbers import \
Real
-# If True, allows components of Vector2 and Vector3 to be set via swizzling;
-# e.g. v.xyz = (1, 2, 3). This is much, much slower than the more verbose
-# v.x = 1; v.y = 2; v.z = 3, and slows down ordinary element setting as
-# well. Recommended setting is False.
-_enable_swizzle_set = False
-
+def add_swizzle(components) :
+
+ def do_swizzle(Vec) :
+
+ def defprop(components) :
+
+ def getter(self) :
+ return \
+ tuple(getattr(self, c) for c in components)
+ #end getter
+
+ def setter(self, values) :
+ if len(values) != len(components) :
+ raise ValueError \
+ (
+ "need %d components for setting %s"
+ %
+ (len(components), repr(components))
+ )
+ #end if
+ for c, v in zip(components, values) :
+ setattr(self, c, v)
+ #end for
+ #end setter
+
+ #begin defprop
+ name = "".join(components)
+ getter.__doc__ = "components %s" % repr(components)
+ getter.__name__ = name
+ return name, property(getter, setter)
+ #end defprop
+
+ def permute_components(components) :
+ for i in range(len(components)) :
+ this = components[i]
+ rest = components[:i] + components[i + 1:]
+ yield (this,)
+ for permrest in permute_components(rest) :
+ yield (this,) + permrest
+ #end for
+ #end for
+ #end permute_components
+
+ #begin do_swizzle
+ for perm in permute_components(components) :
+ if len(perm) > 1 :
+ name, prop = defprop(perm)
+ setattr(Vec, name, prop)
+ #end if
+ #end for
+ return Vec
+ #end do_swizzle
+
+#begin add_swizzle
+ return do_swizzle
+#end add_swizzle
+
+@add_swizzle(("x", "y"))
class Vector2:
__slots__ = ['x', 'y']
__hash__ = None
@@ -101,21 +153,6 @@ class Vector2:
except ValueError:
raise AttributeError(name)
- if _enable_swizzle_set:
- # This has detrimental performance on ordinary setattr as well
- # if enabled
- def __setattr__(self, name, value):
- if len(name) == 1:
- object.__setattr__(self, name, value)
- else:
- try:
- l = [self.x, self.y]
- for c, v in map(None, name, value):
- l['xy'.index(c)] = v
- self.x, self.y = l
- except ValueError:
- raise AttributeError(name)
-
def __add__(self, other):
if isinstance(other, Vector2):
# Vector + Vector -> Vector
@@ -268,6 +305,7 @@ class Vector2:
n = other.normalized()
return (self @ n)*n
+@add_swizzle(("x", "y", "z"))
class Vector3:
__slots__ = ['x', 'y', 'z']
__hash__ = None
@@ -325,22 +363,6 @@ class Vector3:
except ValueError:
raise AttributeError(name)
- if _enable_swizzle_set:
- # This has detrimental performance on ordinary setattr as well
- # if enabled
- def __setattr__(self, name, value):
- if len(name) == 1:
- object.__setattr__(self, name, value)
- else:
- try:
- l = [self.x, self.y, self.z]
- for c, v in map(None, name, value):
- l['xyz'.index(c)] = v
- self.x, self.y, self.z = l
- except ValueError:
- raise AttributeError(name)
-
-
def __add__(self, other):
if isinstance(other, Vector3):
# Vector + Vector -> Vector
@@ -1188,7 +1210,7 @@ class Matrix4:
return tmp;
-
+@add_swizzle(("w", "x", "y", "z"))
class Quaternion:
# All methods and naming conventions based off
# http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions
@@ -1511,6 +1533,8 @@ class Quaternion:
Q.z = q1.z * ratio1 + q2.z * ratio2
return Q
+del add_swizzle # your work is done
+
# Geometry
# Much maths thanks to Paul Bourke, http://astronomy.swin.edu.au/~pbourke
# ---------------------------------------------------------------------------
Cleaner to use the decorator syntax for this:
diff --git a/euclid.py b/euclid.py
index 2f3209a..cd75766 100644
--- a/euclid.py
+++ b/euclid.py
@@ -689,25 +689,26 @@ class Matrix3:
return self
# Static constructors
+ @classmethod
def new_identity(cls):
self = cls()
return self
- new_identity = classmethod(new_identity)
+ @classmethod
def new_scale(cls, x, y):
self = cls()
self.a = x
self.f = y
return self
- new_scale = classmethod(new_scale)
+ @classmethod
def new_translate(cls, x, y):
self = cls()
self.c = x
self.g = y
return self
- new_translate = classmethod(new_translate)
+ @classmethod
def new_rotate(cls, angle):
self = cls()
s = math.sin(angle)
@@ -716,7 +717,6 @@ class Matrix3:
self.b = -s
self.e = s
return self
- new_rotate = classmethod(new_rotate)
def determinant(self):
return (self.a*self.f*self.k
@@ -1001,33 +1001,34 @@ class Matrix4:
return M
# Static constructors
+ @classmethod
def new(cls, *values):
M = cls()
M[:] = values
return M
- new = classmethod(new)
+ @classmethod
def new_identity(cls):
self = cls()
return self
- new_identity = classmethod(new_identity)
+ @classmethod
def new_scale(cls, x, y, z):
self = cls()
self.a = x
self.f = y
self.k = z
return self
- new_scale = classmethod(new_scale)
+ @classmethod
def new_translate(cls, x, y, z):
self = cls()
self.d = x
self.h = y
self.l = z
return self
- new_translate = classmethod(new_translate)
+ @classmethod
def new_rotatex(cls, angle):
self = cls()
s = math.sin(angle)
@@ -1036,8 +1037,8 @@ class Matrix4:
self.g = -s
self.j = s
return self
- new_rotatex = classmethod(new_rotatex)
+ @classmethod
def new_rotatey(cls, angle):
self = cls()
s = math.sin(angle)
@@ -1046,8 +1047,8 @@ class Matrix4:
self.c = s
self.i = -s
return self
- new_rotatey = classmethod(new_rotatey)
+ @classmethod
def new_rotatez(cls, angle):
self = cls()
s = math.sin(angle)
@@ -1056,8 +1057,8 @@ class Matrix4:
self.b = -s
self.e = s
return self
- new_rotatez = classmethod(new_rotatez)
+ @classmethod
def new_rotate_axis(cls, angle, axis):
assert(isinstance(axis, Vector3))
vector = axis.normalized()
@@ -1081,8 +1082,8 @@ class Matrix4:
self.j = y * z * c1 + x * s
self.k = z * z * c1 + c
return self
- new_rotate_axis = classmethod(new_rotate_axis)
+ @classmethod
def new_rotate_euler(cls, heading, attitude, bank):
# from http://www.euclideanspace.com/
ch = math.cos(heading)
@@ -1103,8 +1104,8 @@ class Matrix4:
self.j = sh * sa * cb + ch * sb
self.k = -sh * sa * sb + ch * cb
return self
- new_rotate_euler = classmethod(new_rotate_euler)
+ @classmethod
def new_rotate_triple_axis(cls, x, y, z):
m = cls()
@@ -1113,8 +1114,8 @@ class Matrix4:
m.i, m.j, m.k = x.z, y.z, z.z
return m
- new_rotate_triple_axis = classmethod(new_rotate_triple_axis)
+ @classmethod
def new_look_at(cls, eye, at, up):
z = (eye - at).normalized()
x = up.cross(z).normalized()
@@ -1123,8 +1124,8 @@ class Matrix4:
m = cls.new_rotate_triple_axis(x, y, z)
m.d, m.h, m.l = eye.x, eye.y, eye.z
return m
- new_look_at = classmethod(new_look_at)
+ @classmethod
def new_perspective(cls, fov_y, aspect, near, far):
# from the gluPerspective man page
f = 1 / math.tan(fov_y / 2)
@@ -1137,7 +1138,6 @@ class Matrix4:
self.o = -1
self.p = 0
return self
- new_perspective = classmethod(new_perspective)
def determinant(self):
return ((self.a * self.f - self.e * self.b)
@@ -1393,10 +1393,11 @@ class Quaternion:
return M
# Static constructors
+ @classmethod
def new_identity(cls):
return cls()
- new_identity = classmethod(new_identity)
+ @classmethod
def new_rotate_axis(cls, angle, axis):
assert(isinstance(axis, Vector3))
axis = axis.normalized()
@@ -1407,8 +1408,8 @@ class Quaternion:
Q.y = axis.y * s
Q.z = axis.z * s
return Q
- new_rotate_axis = classmethod(new_rotate_axis)
+ @classmethod
def new_rotate_euler(cls, heading, attitude, bank):
Q = cls()
c1 = math.cos(heading / 2)
@@ -1423,8 +1424,8 @@ class Quaternion:
Q.y = s1 * c2 * c3 + c1 * s2 * s3
Q.z = c1 * s2 * c3 - s1 * c2 * s3
return Q
- new_rotate_euler = classmethod(new_rotate_euler)
+ @classmethod
def new_rotate_matrix(cls, m):
if m[0*4 + 0] + m[1*4 + 1] + m[2*4 + 2] > 0.00000001:
t = m[0*4 + 0] + m[1*4 + 1] + m[2*4 + 2] + 1.0
@@ -1469,8 +1470,8 @@ class Quaternion:
(m[1*4 + 2] + m[2*4 + 1])*s,
s*t
)
- new_rotate_matrix = classmethod(new_rotate_matrix)
+ @classmethod
def new_interpolate(cls, q1, q2, t):
assert isinstance(q1, Quaternion) and isinstance(q2, Quaternion)
Q = cls()
@@ -1506,7 +1507,6 @@ class Quaternion:
Q.y = q1.y * ratio1 + q2.y * ratio2
Q.z = q1.z * ratio1 + q2.z * ratio2
return Q
- new_interpolate = classmethod(new_interpolate)
# Geometry
# Much maths thanks to Paul Bourke, http://astronomy.swin.edu.au/~pbourke
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.