Giter Club home page Giter Club logo

narang's Introduction

Project Narang

개인의 감정을 케어하는 웹서비스

project narang

Demo site

https://narang.ml

참여자

@ohoraming @kkn1125

기획

"Narang"서비스는 일기를 작성하면서 감정을 분석하고 해석된 감정 데이터를 그래프로 변환하여 주 단위로 자신을 관찰하는 감정 케어 서비스입니다.

컨셉

  1. TXT
    네이버 파파고 API로 일기 내용을 번역하고, 번역된 내용을 sentiment 라이브러리로 감정 분석합니다. 일기 하단에는 일기 전체의 감정을 나타내는 이모지를 표시합니다.
  2. AI
    등록된 안면 이미지를 조회하여 AI기술을 통해 사용자와 일치 여부를 판별하고, 간편하게 로그인합니다. 안면 인식 로그인은 프로필에서 자신의 사진을 업로드하면 자동 활성화 됩니다.
  3. GRAPH
    분석된 감정 데이터는 Chart.js를 이용해 그래프로 표시합니다. 그래프를 통해 나의 감정을 케어하는 서비스를 사용할 수 있습니다.

서비스 목록

  1. 안면 인식 로그인 (face-api.js detectFace)
  2. 소셜 계정 로그인 (카카오 로그인 API)
  3. 일기
    • 일기 내용을 기반으로 감정 판별
    • 감정 분석
    • 감정 분석 데이터 기반 그래프 (주 단위) 생성

안면 인식 로그인

default.mp4

일기 쓰기 & 감정 분석 그래프

default.mp4

댓글 + 좋아요

2.mp4

멘션 확인

_.mp4

웹 예시 소셜 로그인
웹 예시 카카오_로그인_짧음
안면 로그인 정확도 테스트 -
안면로그인_정확도_테스트_저용량 -

기술 스택

image

개발 환경

  1. Common
    • babel 7.18.10
    • prettier 2.7.1
  2. Front-end
    • react 18.2.0
    • typescript 4.8.2
    • http-proxy-middleware 2.0.6
    • html-react-parser 3.0.1
    • formik 2.2.9
    • yup 0.32.11
    • jose 4.8.3
    • js-sha256 0.9.0
    • material-ui 5.10.2
    • emotion 11.10.0
    • kadvice 1.0.10
    • suneditor 2.43.14
    • react-cookie 4.1.1
    • react-dom 18.2.0
    • react-router-dom 6.3.0
    • chart.js 3.8.2
    • react-chartjs-2 4.3.1
    • dotenv 16.0.1
    • date-fns 2.29.1
    • axios 0.27.2
    • naver papago - 번역
  3. Back-end
    • java openJDK 11
    • Spring Boot 2.7.1
    • Spring Security 5.7.2
    • Json Web Token 0.11.5
    • Lombok 1.18.24
    • jackson data-bind 2.13.3
    • common-io 2.11.0
    • gradle 7.4.2
  4. AI
    • sentiment - 텍스트 감정 분석 5.0.2
    • face-api.js - 안면 인식 + 감정 분석 + 인물 매칭 0.22.2
  5. Deploy
    • AWS EC2
    • NGINX 1.20.1
    • mobaXterm

Database 명세서

  • MongoDB v5.3.1
  • Mongosh v1.1.7

Database - narang drawio

Project Narang Database Docs

API 문서 & 명세서

Project Narang API Docs

Mock Image

Mock Image

narang's People

Contributors

kkn1125 avatar ohoraming avatar

Stargazers

 avatar

Watchers

 avatar

Forkers

ohoraming

narang's Issues

[FE] Axios 테스트 코드 문제

jest 사용 시 axios 테스트 방법

Mocking 데이터로 api테스트는 가능, 하지만 가짜 데이터다 보니 실제로 db에서 넘어오는 ObjectId값을 보고 테스트하기 어려움

api로 가져오는 테스트 등은 일반 컴포넌트에서 테스트 진행해야 함.


결론

mocking 데이터로 User Model 클래스 테스트 등 로직 테스트는 가능

[FE] Profile 페이지 formik + yup 검증 구현

Profile page form 데이터 검증

  • 프로필 및 face Sign을 위한 이미지 둘 다 file 객체 검증 필요
  • 기타 input 필드에 대한 검증은 회원가입, 로그인 등에 사용되는 필드 검증을 재사용

[BE] restController 에서 method 다중 설정 문제

문제 상황

controller 작성 중 동일 경로에 method를 둘 이상 사용할 상황이 생김. @PostMapping, @PutMapping 어노테이션을 작성하여 테스트 한 결과 에러 발생.

해결 방안

@RequestMapping을 사용하여 method를 두개 이상 작성하여 동일 path로 설정해서 사용한다.

예시 코드는 다음과 같다.

@RequestMapping(path="/emotion", method={RequestMethod.POST, RequestMethod.PUT})
public Boolean save(Emotion emotion) {
  // logics ...
  return true;
}

[FE/BE] 공통 규칙

FE/BE 코드 규칙 설정

공통 사항

  1. 모델 객체 필드 값은 디폴트 값 없이 사용하는 것을 원칙으로 한다. (데이터 value를 의도적으로 제한하기 위함)
  2. 디폴트를 제거하는 다른 이유 : 부득이 null값이 들어가야할 때가 있다. 그럴 때 null값을 다른 값으로 디폴트 처리하면 코드 작업이 더 딜레이 될 것으로 생각하여 판단 함.

FE 공통 규칙

프론트에 생성하는 객체는 데이터를 주고 받는 그릇으로 활용하기 위해 생성. 공통으로 사용하는 함수를 개발해야 api개발에 사용할 때 편의성을 증대 시킬 수 있다. 유지보수성을 높히기 위한 이유도 포함.

Model

  • 소문자 camelCase로 변수명을 작성한다.
  • private으로 외부에서 접근 못하도록 설정한다.
  • PModel의 함수에 따르고 IModel에서 상속 받는 추상 함수는 재정의 할 때 타입을 준수한 범위 내에서 모든 코드 구조는 변경 가능하다.
  • 디렉토리는 src/apis/해당클래스명/index.ts 로 작성한다. type 설정이 많아지면 index.ts와 같은 위치에 types.ts를 생성하여 모듈화한다.
  • export는 default가 아닌 객체로 내보내는 것을 기본으로 한다.
  • 디렉토리에서 기능별로 나누려면 src/apis/기능명/index.ts 로 작성한다.
  • 공통의 api는 src/utils/index.ts 에 작성한다. 공통의 type은 src/apis/commonTypes.ts에 정의한다.

IModel의 개념

interface Model을 의미한다.

interface로서 모든 클래스에 필요한 공통의 함수명과 인자, 반환 값을 지정한다.

PModel의 개념

extends 하여 사용하기 위한 클래스로 공통으로 타입에 관계없이 사용할 수 있도록 확장하여 활용하기 위한 목적으로 만들어졌다.

// IModel과 PModel을 상속한 예시 코드
class User extends PModel implements IModel {
  private nickName: ModelStringValue;
  //...

  public set(column: UserColumn, value: ModelValue) {
    // ...
  }
}

API

api는 아래 규칙을 따른다.

  1. api 작업 시 #41 의 내용을 토대로 생성하며, 수정 사항 발생 시 정리하여 #41 에 추가로 코멘트를 생성한다.
  2. 수정사항은 백엔드 restController와 충분히 테스트 및 검토 후 반영한다.
  3. 함수 명칭은 MongoRepository 기준으로 하며 예시는 다음과 같다.
const findUserAll () { /* ... */ }
const findUserById (id: string) { /* ... */ }
const findDiaryByUid (uid: string) { /* ... */ }
const insertUser (user: User) { /* ... */ }
const updateUser (user: User) {
  const formData = user.makeFormData();
  return axios.put('/api/user', formData)
    .then(handleReceiveData)
    .catch(handleReceiveError);
}
const deleteUserById (id: string) { /* ... */ }

BE 공통 규칙

Entity 클래스

  • 소문자 camelCase로 변수명을 작성한다.
  • private으로 외부에서 접근 못하도록 설정한다.
  • update가 필요한 클래스에 한해서 Entity 클래스를 확장해서 replaceIfNotNull을 사용한다.
  • 객체 update에 필요한 함수 replaceIfNotNull 내용은 아래와 같다.
    public class Entity<T> {
        public T replaceIfNotNull(T compare) {
            List<Field> fields = Arrays.asList(this.getClass().getDeclaredFields());
            fields.forEach(field -> {
                try {
                    field.setAccessible(true);
                    Object compareField = field.get(compare);
                    Object thisField = field.get(this);
                    if (field.getName() == "userAuth") {
                        field.set(this, "USER");
                    } else {
                        field.set(this, compareField != null ? compareField : thisField);
                    }
                } catch (IllegalAccessException e) {
                    System.out.println("The value is null [" + field.getName() + "]");
                }
            });
            return (T) this;
        }
    }
    
    // 사용 예시
    public class User extends Entity<User> implements UserDetails {
      // ...
    }

Repository

  • 리파지토리 명칭은 ClassNameRepository로 한다.

  • config디렉토리 MongoAuditConfig에 지정해두었기 때문에 @repository는 달지 않는다.

  • ClassNameRepository : interface

  • ClassNameRepositoryCustom : interface - 커스터마이징 구현 함수 설정

  • ClassNameRepositoryCustomImpl : class - 커스터마이징 구현 함수 작성 (MongoTemplate를 생성자 주입으로 사용하는 것을 원칙으로 한다.)

기본적으로 단순한 findAll, findById, insert, deleteById는 ClassNameRepository에서 사용하고, 쿼리문이 필요한 함수만 커스터마이징하여 사용한다.

Service

  • restController를 무겁게 하는 것을 피하기 위해 Service는 필수로 사용한다.
  • Service에서 해당하는 Repository를 생성자 주입으로 사용하는 것을 원칙으로 한다.

함수 명칭은 ClassNameRepositoryCustomImpl, ClassNameService, ClassNameRestController 모두 동일하게 처리한다. 예를 들면 다음과 같다.

// DiaryRepositoryCustomImpl
class DiaryRepositoryCustomImpl implements DiaryRepositoryCustom {
  private MongoTemplate diaryTemplate;
  
  @Autowired
  DiaryRepositoryCustomImpl(MongoTemplate diaryTemplate) {
    this.diaryTemplate = diaryTemplate;
  }
  
  public Optional<User> findByUid (String uid) {
    Criteria cr = new Criteria("uid").is(uid);
    Query q = new Query(cr);
    return diaryTemplate.findOne(q, Diary.class);
  }
}

// DiaryService
@Service
class DiaryService {
  private DiaryRepository diaryRepository;
  
  @Autowired
  DiaryRepositoryCustomImpl(DiaryRepository diaryRepository) {
    this.diaryRepository= diaryRepository;
  }
  
  public User findByUid (String uid) {
    return diaryRepository.findByUid(uid).orElseThrow();
  }
}

// DiaryRestController
@RestController
@RequestMapping("/api")
class DiaryRestController {
  private DiaryService diaryService;
  
  @Autowired
  DiaryRepositoryCustomImpl(DiaryService diaryService) {
    this.diaryService= diaryService;
  }

  private String mapper(Object object) throws JsonProcessingException {
    return new ObjectMapper().writeValueAsString(object);
  }
  
  @GetMapping("/user/uid/{uid}")
  public String findByUid (@PathVariable("uid") String uid) throws JsonProcessingException {
    return mapper(diaryService.findByUid(uid));
  }
}

[FE] face-api 영상 인식 이슈

영상 안면 인식 테스트

영상 안면 인식 테스트 진행 결과

  • 안면 프레임 인식
  • 표정 인식
  • 라벨링 이미지 매칭

[FE] face-api warning 에러 문제

문제 상황

cra start시 다수의 warning에러 발생

원인

face-api가 예전 버전과 달리 더 이상 브라우저 용 라이브러리가 아니기 때문에 fs 등 node환경의 라이브러리를 불러올 수 없기 때문에 node_modules에서 파일을 찾지 못하는 에러 발생

해결방안

아래 링크를 참조하여 환경변수 수정으로 쉽게 해결가능 함.

Stackoverflow 설정은 이 링크 참조
기타 cra에서 warning 제거 가능한 설정 참조
cra에서 환경변수 설정 가능한 키 값 설명 참조

[BE] 백엔드 계획 의논

백엔드 DB + API 설계

각 클래스 공통 필드명

  • id
  • regdate
  • updates
  • _class

DB

  • User
    • nickname
    • email
    • password
    • profileImg
    • phone
    • isFaceSign
  • FaceImage
    • uid
    • imgPath
  • Diary
    • uid (추가)
    • title
    • content
    • author
    • isShare
  • Emotion
    • uid (추가)
    • did (추가)
    • advice
    • comparative
    • emoji
    • negative: Whether
    • normal: Normal
    • positive: Whether
    • score
      • Whether (추가)
        • score
        • count
        • words
      • Normal (추가)
        • count
  • Comment
    • did (추가)
    • content
    • author
    • mention
  • Product (변경)
    • category
    • pid
    • name
    • price
    • amount
    • content
    • seller
    • isSoldOut
    • regdate
    • updates
  • Cart
    • cid: 계속 변경 hash
    • uid
    • pid
    • amount
    • isOrdered
    • regdate
    • updates
  • Bill
    • bid
    • pid
    • uid
    • amount
    • price
    • regdate
  • Like
    • uid
    • did
    • isLike

[COMMON] 라벨 및 깃 컨벤션

이슈 라벨

  1. bugfix
  2. update
  3. add
  4. delete
  5. todo
  6. ref

깃 컨벤션

커밋 메세지

입력 형식
[해당 분야][이모지]: 간략한 설명

입력 예시
[FE][:bulb: ADD]: url수정
[COMMON][:hammer_and_wrench: FEAT]: UI 수정 및 chart.js 버전업
[BE][:lady_beetle: BUGFIX]: graph 랜더링시 떨림 현상 수정
[FE][:wrench: FIX:]: main 화면 오타 수정

  • 💡 ADD: 추가
  • 🛠️ FEAT: 수정과 추가 혼합 시
  • 🐞 BUGFIX: 버그 발생 시 수정 - 버그 내용 달아줘야 함
  • 🔧 FIX: 단순 수정

브랜치 명

main - develop

  • 작업 순서
    • 작업 후 develop 브랜치에 commit and push
    • 충돌 확인 후 main 브랜치에 최종 merge

브랜치 키워드

  • fix
  • add
  • feat
  • delete

브랜치 작성

형식: 작성자ID/키워드
예시: ohoraming/add

[COMMON] env 환경변수 모듈화 작업 문제 발생

문제상황

환경변수 경로 설정 시 fs 관련 에러 발생 함. export해도 아예 dotenv config 메서드 자체가 작동 안 함.
dotenv의 config는 setupProxy파일에서는 정상 작동하나 runtime에서 실행될 때 파일에서는 별도 설정이 필요한 것으로 보임.

Stackoverflow 자료 참고

[BE] spring security

문제 상황

단기간에 security를 익혀서 적용하기 어려움.

jwt 사용 방법을 익혀서 아래 순서로 진행하는 것으로 생각 중.

jwt 적용 순서

로그인 요청 -> (서버) 해시 처리 된 패스워드와 받아온 패스워드 해시 처리하여 대조 -> (서버) 일치하면 jwt 토큰 발행
-> (클라) 받은 토큰 정보를 로컬 스토리지 or react-cookies로 cookie에 저장

편리한 방법은 react-cookies로 cookieProvider로 전역 상태 관리 가능하므로 react-cookies로 개발.

개선 사항

  • 이 방법 또한 탈취의 위험이 있다는 것을 인지
  • (서버) refresh token 로직은 다음에 설정하는 것으로 함.

[BE] emotion api 생성

수정사항

emotion 클래스에 did필드가 누락되어 추가 함.
Whether class 테스트
class 내에 subClass가 있을 경우 postman에서 아래와 같은 형식으로 데이터 전달이 되어야 한다.
즉, formData또한 name 값이 아래와 같아야 한다.

formData name 명명 규칙

positive.scroe = 0
positive.words[0] = 'well'

위와 같은 형식의 name과 value쌍이어야 한다.

post, put method 설정

각 어노테이션은 spring에서 함수를 따로 작성하여야 한다.

put요청 시 _id값을 파라미터 변수로 받아 setter로 해당 객체 변수에 따로 지정하는 구문이 있어야 한다.

예시 코드는 다음과 같다.

@PutMapping("/emotion")
public Boolean update(Emotion emotion, String _id) {
  System.out.println(emotion);
  emotion.set_id(_id);
  emotionTemplate.update(emotion);
  return true;
}

위 코드와 같이 emotion객체를 받아오면 _id값은 null인체로 가져와진다. emotion class에서 _id필드는 @field가 아닌 @id 어노테이션이 적용된다. 해당 어노테이션이 이유인지는 모르겠으나 @field로 지정된 필드는 모두 불러오기 때문에 자세한 내용을 파악되는데로 이슈에 내용을 붙일 예정

결론

@id 어노테이션이 붙은 필드는 setter로 따로 지정해주는 구문이 필요하다는 결론.

[BE] mongoDB datetime 자동 처리 문제

MongoDB datetime 필드 자동 처리

mongo audit 설정 참고 - 5장 MongoDB 5장 Audit with Spring


문제 원인

mongodb에서 자동으로 CreatedDate과 lastModifiedDate을 설정하는 방법은 여러가지 있다. 쉽고 간편한 방법은 @CreatedDate와 @LastModifiedDate 어노테이션이 있다. 이 설정이 잘 작동 되게 하려면 MongoDB에서 따로 audit이라는 설정을 해줘야 MongoDB에 접근하여 데이터 수정이 가능하게 하는 듯 하다.

자세한 사항은 찾아봐야하며 정리되는데로 이슈에 기록해 둘 것.


해결

MongoDB audit 설정은 단순하다. MongoAuditConfig를 생성하고 몇가지 어노테이션으로 Configure설정 등을 해주고, 각 Model Class마다 Auditor를 설정하면 된다.

// java/com.../config/MongoAuditConfig.java

package com.narang.web.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.EnableMongoAuditing;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

@Configuration
@EnableMongoRepositories("com.narang.web.mongoTemplate")
@EnableMongoAuditing
public class MongoAuditConfig {
}
// java/com.../config/UserAuditor.java

package com.narang.web.config;

import org.springframework.data.domain.AuditorAware;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component("mongoAuditingConfig")
public class UserAuditor implements AuditorAware<String> {
    @Override
    public Optional<String> getCurrentAuditor() {
        return Optional.of("narang");
    }
}

참고 링크

Stackoverflow dateTime 타입 설정 어노테이션 참조
Stackoverflow 어노테이션 사용 내용 참조
Stackoverflow null, blank 설정 예시 코드 참조

[BE] MongoDB api findById 호출 시 문제

MongoDB api findById 호출 시 문제

문제 상황

postman 또는 프론트단에서 api호출 axios로 호출 시 결과 값 null이 발생

원인

User 클래스에 _id의 값이 Field 어노테이션이 달려있어서 발생 함.
즉, _id는 Id어노테이션으로 지정되어 있어 ObjectId를 가리키는데 Field어노테이션은 Id외에 할당해야 문제가 안 됨.

[BE] Id 필드 자동 생성 안되는 문제

문제 상황

  1. 객체 insert시 id값이 null로 가면 400 에러 발생
  2. 객체 insert시 id값이 '' 로 가면 '' 그대로 아이디가 지정

문제 해결 방안

id필드를 formData에 넣지 않고 post요청하면 id필드 자체가 없는 것으로 spring에서 받을 때 null값이 할당되며, 데이터베이스 왔다갔다 할 때 자동으로 objectId 부여

[FE] 회원 가입 관련 사항

차트 수정을 위해 회원 가입을 하다가 수정하면 좋을 것 같아 의견 남깁니다!
(수정 중이었다면 이번 이슈 닫으셔도 됩니당)

  1. 이메일 형식
    • 특수 문자(.) 삽입 불가
      image

    • 언더바(_)는 삽입됩니다
      image

  2. 영문 소문자가 없으면 비밀번호로 인정되지 않는 이슈
    • 1234QWER!입력시
      image

    • 1234QWER!a입력시
      image

  3. 전화번호 입력시 번호 형식을 알려주면 좋을 것 같습니다
    image

초기안

기획 목적

컨셉

자가감정케어서비스

기능

  1. 안면인식
  • 로그인
  • 감정인식
  • 페이스아이디
  1. 로그인 기능 (소셜 로그인 포함)
  2. 일기쓰기 (게시판)
  • 맞춤법 api 조사해서 감정 판별
  • 사진/영상/음성만 업로드 (voice to text 조사 필요)
  1. 판매 시스템
  • 감정 인식 -> 기분에 따라 굿즈 추천
  • 광고주 우선순위
  1. 결제 시스템
  • 결제 시스템 구현 조사 필요
  1. 분석 섹션
  • 이모션 그래프 (주, 월, 연 단위) - ref : chart.js

보류 기술

  1. 영상편집
  2. 화상채팅

사용기술

  • aws 서버 배포

front-end

  • react (typescript)
  • react-router-dom
  • mui + emotion - css 부분
  • axios
  • http-proxy-middleware
  • face-api.js - 안면인식
  • 맞춤법api (조사)
  • dotenv - 환경변수
  • formidable - 파일 업로드 라이브러리
  • formik - form 데이터 검증 라이브러리
  • chart.js - 이모션 그래프
  • yup - form 데이터 검증
  • prop-types - prop의 타입 검증

back-end

  • spring boot
  • lombok
  • spring security

Database

  • 개발 : MongoDB
  • prod : mysql + jpa

참고자료

프로젝트 초기화

java8 vs java11

face-api

aws 배포방법

jar 배포 방법

[BE] faceimage api 생성

변경사항

  • faceImage api를 다음과 같이 변경
  • faceImage class의 필드 변경

faceImage fields

regdate 및 update 필드는 모든 클래스에 공통 적용하는 것으로 해서 각 데이터의 생성일자 등을 관리하는 데 사용한다.
uid를 추가하여 고유한 _id를 모든 클래스가 가지면서 추후 관계형 데이터베이스로 변경 용이하도록 각 테이블 간 종속성을 부여하기 위해 필요한 타 테이블의 _id값을 받도록 설계하기 위함.


문제 발생

문제는 여러 아이디 값을 받아 쿼리 조건문으로 처리하는 것이 효율적이라 판단하여 쿼리문에 조건문을 테스트 하였음.

문제 발생 상황은 아래와 같다.

  1. mongotemplate의 operator를 사용해서 andOperator를 사용하였음.
  2. mongodb compass에서 테스트 결과 동작하지 않음
  3. orOperator를 사용
  4. 다중 삭제 및 mongodb compass 정상작동

결론

query문 조건에서 or과 and를 다시 생각해보고 고친 것이라 query 조건에 대해 아직 헷갈리는 부분이 있다고 생각.
조건문에 대해서 api작업을 진행하면서 익힐 필요가 있음.

[FE] Chart 디자인 및 데이터 적용

일기_텍스트_분석.pdf

샘플 데이터 10개를 분석한 결과(PDF파일 참조),

  • negative 점수 (음수)
  • positive 점수 (양수)
  • total score (합산 점수)

negativepositive 점수를 막대 그래프로 보여주고,
합산 score꺾은선 그래프로 보여주는 것이 효과적으로 판단됨

stack 버전과 기본버전 두 가지를 구현했는데,
추후 선택 필요

  • stack 버전
    image

  • 기본버전
    image

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.