├── .gitignore
├── .travis.yml
├── CONTRIBUTING.md
├── README.md
├── demo
└── src
│ ├── .babelrc
│ ├── Barchart.js
│ ├── Recaman.js
│ ├── images
│ └── BBLogo.png
│ ├── index.js
│ ├── prism.css
│ ├── socialShare.js
│ └── style.css
├── nwb.config.js
├── package-lock.json
├── package.json
├── src
├── index.ts
└── types.d.ts
├── tests
├── .eslintrc
└── index-test.js
├── tsconfig.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | /coverage
2 | /demo/dist
3 | /es
4 | /lib
5 | /node_modules
6 | /umd
7 | npm-debug.log*
8 | src/index.js
9 | src/index.js.map
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: node_js
4 | node_js:
5 | - 8
6 |
7 | before_install:
8 | - npm install codecov.io coveralls
9 |
10 | after_success:
11 | - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js
12 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
13 |
14 | branches:
15 | only:
16 | - master
17 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Prerequisites
2 |
3 | [Node.js](http://nodejs.org/) >= v4 must be installed.
4 |
5 | ## Installation
6 |
7 | - Running `npm install` in the component's root directory will install everything you need for development.
8 |
9 | ## Demo Development Server
10 |
11 | - `npm start` will run a development server with the component's demo app at [http://localhost:3000](http://localhost:3000) with hot module reloading.
12 |
13 | ## Running Tests
14 |
15 | - `npm test` will run the tests once.
16 |
17 | - `npm run test:coverage` will run the tests and produce a coverage report in `coverage/`.
18 |
19 | - `npm run test:watch` will run the tests on every change.
20 |
21 | ## Building
22 |
23 | - `npm run build` will build the component for publishing to npm and also bundle the demo app.
24 |
25 | - `npm run clean` will delete built resources.
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # d3blackbox
2 |
3 | [![npm package][npm-badge]][npm]
4 |
5 | Take any D3 example you find in the wild and wrap it in a React component. Great for quick experiments and meeting deadlines. 😛
6 |
7 | ## Meet your deadline in 2 minutes
8 |
9 | Check out how it works in [a live Codesandbox](https://codesandbox.io/s/9jm82v2lry)
10 |
11 | 1. Install `d3blackbox` with npm
12 |
13 | ```
14 | $ npm install -s d3blackbox
15 | ```
16 |
17 | 2. Find a D3 example you like
18 | 3. Copy its code, put it in a function
19 | 4. Wrap function in `D3blackbox`
20 |
21 | ```javascript
22 | import React from "react";
23 | import D3blackbox from "d3blackbox";
24 | import * as d3 from "d3";
25 |
26 | const MyDataviz = D3blackbox(function(anchor, props, state) {
27 | const svg = d3.select(anchor.current);
28 |
29 | // the rest of your D3 code
30 | });
31 |
32 | export default MyDataviz;
33 | ```
34 |
35 | 5. Render inside an `` element
36 | 6. Enjoy your blackbox D3 component
37 |
38 | D3blackbox renders an anchor element and delegates control to your render function. You get an `anchor` ref, the component's `props`, and `state`. Do what you want :)
39 |
40 | Great for meeting deadlines and playing around with other people's code. Not recommended for large scale use due to performance constraints. Your render function runs on every component update and redraws the entire DOM subtree. React's rendering engine can't help you.
41 |
42 | That's why it's called a sandbox.
43 |
44 | ## What if I don't use D3?
45 |
46 | That's okay. `D3blackbox` lets you delegate control to any rendering library you want. As long as you're okay rendering into a `` element.
47 |
48 | You can even use this approach to render Vue apps inside your React apps. 🤨
49 |
50 | ## What if I don't want ``?
51 |
52 | That's okay you weirdo. Use the `component` argument to specify a different component.
53 |
54 | ```javascript
55 | render() {
56 | return
57 | }
58 | ```
59 |
60 | ## HOCs are so September, do you have hooks?
61 |
62 | Yes. Hooks are alpha support and all that, but here's how you can use D3 blackbox as a React hook.
63 |
64 | ```javascript
65 | import { useD3 } from "d3blackbox";
66 |
67 | function renderSomeD3(anchor) {
68 | d3.select(anchor);
69 |
70 | // ...
71 | }
72 |
73 | const MyD3Component = ({ x, y }) => {
74 | const refAnchor = useD3(anchor => renderSomeD3(anchor));
75 |
76 | return ;
77 | };
78 | ```
79 |
80 | ## With love ❤️
81 |
82 | Built with love by Swizec
83 | Cheers
84 |
85 | Only took me 2 years to get around to opensourcing this 😅
86 |
87 | [npm-badge]: https://img.shields.io/npm/v/npm-package.png?style=flat-square
88 | [npm]: https://www.npmjs.org/package/npm-package
89 |
--------------------------------------------------------------------------------
/demo/src/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | [
4 | "prismjs",
5 | {
6 | "languages": [
7 | "javascript",
8 | "css",
9 | "markup"
10 | ],
11 | "plugins": [
12 | "line-numbers"
13 | ],
14 | "theme": "solarized-light",
15 | "css": true
16 | }
17 | ]
18 | ]
19 | }
--------------------------------------------------------------------------------
/demo/src/Barchart.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import D3blackbox from "../../src";
3 | import * as d3 from "d3";
4 |
5 | const Barchart = D3blackbox(function(anchor, props) {
6 | var svg = d3.select(anchor.current),
7 | margin = { top: 20, right: 20, bottom: 30, left: 40 },
8 | width = +props.width - margin.left - margin.right,
9 | height = +props.height - margin.top - margin.bottom;
10 |
11 | var x = d3
12 | .scaleBand()
13 | .rangeRound([0, width])
14 | .padding(0.1),
15 | y = d3.scaleLinear().rangeRound([height, 0]);
16 |
17 | var g = svg
18 | .append("g")
19 | .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
20 |
21 | d3.tsv(
22 | "https://cdn.rawgit.com/mbostock/3885304/raw/a91f37f5f4b43269df3dbabcda0090310c05285d/data.tsv",
23 | function(d) {
24 | d.frequency = +d.frequency;
25 | return d;
26 | }
27 | ).then(function(data) {
28 | x.domain(
29 | data.map(function(d) {
30 | return d.letter;
31 | })
32 | );
33 | y.domain([
34 | 0,
35 | d3.max(data, function(d) {
36 | return d.frequency;
37 | })
38 | ]);
39 |
40 | g.append("g")
41 | .attr("class", "axis axis--x")
42 | .attr("transform", "translate(0," + height + ")")
43 | .call(d3.axisBottom(x));
44 |
45 | g.append("g")
46 | .attr("class", "axis axis--y")
47 | .call(d3.axisLeft(y).ticks(10, "%"))
48 | .append("text")
49 | .attr("transform", "rotate(-90)")
50 | .attr("y", 6)
51 | .attr("dy", "0.71em")
52 | .attr("text-anchor", "end")
53 | .text("Frequency");
54 |
55 | g.selectAll(".bar")
56 | .data(data)
57 | .enter()
58 | .append("rect")
59 | .attr("class", "bar")
60 | .attr("x", function(d) {
61 | return x(d.letter);
62 | })
63 | .attr("y", function(d) {
64 | return y(d.frequency);
65 | })
66 | .attr("width", x.bandwidth())
67 | .attr("height", function(d) {
68 | return height - y(d.frequency);
69 | });
70 | });
71 | });
72 |
73 | export default Barchart;
74 |
--------------------------------------------------------------------------------
/demo/src/Recaman.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import * as d3 from "d3";
3 |
4 | import { useD3 } from "../../src";
5 |
6 | // Recaman sequence animation borrowed from http://blockbuilder.org/johnwalley/25b77d49bbbee7aef480d7598708e039
7 |
8 | function recaman(svg, width, height) {
9 | var n = 60;
10 |
11 | var curr = 0;
12 | var seq = [curr];
13 |
14 | for (var i = 1; i < n; i++) {
15 | var next = curr - i;
16 |
17 | if (next < 0 || seq.includes(next)) {
18 | curr = curr + i;
19 | seq.push(curr);
20 | } else {
21 | curr = next;
22 | seq.push(next);
23 | }
24 | }
25 |
26 | console.log(seq);
27 |
28 | var data = [];
29 | var sign = 1;
30 |
31 | for (var i = 0; i < n - 1; i++) {
32 | var center = (seq[i] + seq[i + 1]) / 2;
33 | var radius = Math.abs(seq[i] - seq[i + 1]) / 2;
34 | var dir = Math.sign(seq[i + 1] - seq[i]);
35 | data.push({ center: center, radius: radius, sign: sign, dir: dir });
36 | sign = -sign;
37 | }
38 |
39 | console.log(data);
40 |
41 | var g = d3.select(svg).append("g");
42 |
43 | var x = d3
44 | .scaleLinear()
45 | .range([0, width])
46 | .domain([0, d3.max(seq)]);
47 |
48 | var arc = d3
49 | .arc()
50 | .innerRadius(function(d) {
51 | return x(d.radius);
52 | })
53 | .outerRadius(function(d) {
54 | return x(d.radius);
55 | })
56 | .endAngle(function(d) {
57 | return (
58 | d.dir * d.sign * (Math.PI / 2) + ((d.sign - 1) * Math.PI) / 2
59 | );
60 | })
61 | .startAngle(function(d) {
62 | return (
63 | -d.dir * d.sign * (Math.PI / 2) + ((d.sign - 1) * Math.PI) / 2
64 | );
65 | });
66 |
67 | var DURATION = 300;
68 |
69 | g.selectAll("path")
70 | .data(data)
71 | .enter()
72 | .append("path")
73 | .attr("stroke", "steelblue")
74 | .attr("stroke-width", "2")
75 | .attr("d", arc)
76 | .attr("transform", function(d) {
77 | return "translate(" + x(d.center) + ",240)";
78 | })
79 | .attr("stroke-dasharray", function(d) {
80 | return this.getTotalLength() + ", " + this.getTotalLength();
81 | })
82 | .attr("stroke-dashoffset", function(d) {
83 | return this.getTotalLength();
84 | })
85 | .transition()
86 | .delay(function(d, i) {
87 | return i * DURATION;
88 | })
89 | .duration(DURATION * 2)
90 | .ease(d3.easeLinear)
91 | .attr("stroke-dashoffset", 0);
92 | }
93 |
94 | const Recaman = ({ x, y, width, height }) => {
95 | const refAnchor = useD3(anchor => recaman(anchor, width, height));
96 |
97 | return ;
98 | };
99 |
100 | export default Recaman;
101 |
--------------------------------------------------------------------------------
/demo/src/images/BBLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Swizec/d3blackbox/d11ac5611f9fe7f8419a800727d9658122125d7d/demo/src/images/BBLogo.png
--------------------------------------------------------------------------------
/demo/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { render } from "react-dom";
3 | import SocialShare from './socialShare'
4 | import Barchart from "./Barchart";
5 | import Recaman from "./Recaman";
6 | import styled from 'styled-components';
7 | import Logo from './images/BBLogo.png'
8 | import "./style.css";
9 | import Prism from 'prismjs';
10 | import "./prism.css";
11 |
12 |
13 | const Wrapper = styled.div`
14 | margin: 0;
15 | padding: 0;
16 | color: #2B2B2B;
17 | @keyframes HeroAnimation {
18 | 0% {
19 | opacity: 0;
20 | transform: translateY(20px);
21 | }
22 | 100% {
23 | opacity: 3;
24 | transform: translateY(0);
25 | }
26 | }
27 | .social {
28 | text-align: center;
29 | margin: 0 0 5rem;
30 | }
31 | `
32 | const WrapperHero = styled.div`
33 | background-color: #ffc600;
34 | background-image: linear-gradient(45deg, #ffc600 17%, #faef5e 75%);
35 | text-align: center;
36 | height: 100%;
37 | margin: 0;
38 | padding: 6rem 0;
39 | color: #2B2B2B;
40 | img {
41 | animation: HeroAnimation;
42 | animation-duration: 6s;
43 | animation-fill-mode: forwards;
44 | animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
45 | }
46 | h1 {
47 | color: #2B2B2B;
48 | margin: 0rem 0 5rem;
49 | font-weight: 900;
50 | font-size: 70px;
51 | animation: HeroAnimation;
52 | animation-duration: 4s;
53 | animation-fill-mode: forwards;
54 | animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
55 | }
56 | p {
57 | margin: 0rem auto 3rem;
58 | padding: 0 1rem;
59 | max-width: 800px;
60 | font-weight: 400;
61 | font-size: 28px;
62 | animation: HeroAnimation;
63 | animation-duration: 4s;
64 | animation-fill-mode: forwards;
65 | animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
66 | }
67 | strong {
68 | font-weight: 900;
69 | }
70 | a {
71 | color: #fff;
72 | background-color: #DE541E;
73 | font-weight: 900;
74 | padding: .8rem 2.4rem;
75 | transition: 1s cubic-bezier(0.2, 0.8, 0.2, 1);
76 | }
77 | a:hover {
78 | background-color: #2B2B2B;
79 | color: #fff;
80 | border: none;
81 | box-shadow: 0px 10px 20px rgba(0,0,0,0.5);
82 | transform: translateY(-9px);
83 | }
84 | @media (max-width: 940px) {
85 | h1 {
86 | font-size: 40px;
87 | }
88 | p{
89 | font-size: 20px;
90 | }
91 | }
92 | `
93 |
94 | const WrapperUsage = styled.div`
95 | margin: 5rem auto;
96 | max-width: 800px;
97 | padding: 0 1.5rem;
98 | `
99 | const WrapperGetStart = styled.div`
100 | margin: 5rem auto;
101 | max-width: 800px;
102 | padding: 0 1.5rem;
103 | `
104 | const WrapperHooks = styled.div`
105 | margin: 5rem auto;
106 | max-width: 800px;
107 | padding: 0 1.5rem;
108 | svg {
109 | width: 800;
110 | height: 400;
111 | }
112 | `
113 | const WrapperAnchor = styled.div`
114 | margin: 5rem auto;
115 | max-width: 800px;
116 | padding: 0 1.5rem;
117 | `
118 | const WrapperFooter = styled.div`
119 | background-color: #2B2B2B;
120 | text-align: center;
121 | height: 100%;
122 | padding: 1rem 0;
123 | color: #fff;
124 | display: grid;
125 | align-items: center;
126 | justify-items: center;
127 | grid-template-columns: 1fr 1fr 1fr;
128 | grid-template-areas:
129 | "BU WL YS";
130 | @media (max-width: 940px) {
131 | grid-template-columns: 1fr;
132 | grid-template-areas:
133 | "BU"
134 | "WL"
135 | "YS";
136 | }
137 | h2 {
138 | grid-area: WL;
139 | }
140 | .Built {
141 | grid-area: BU;
142 | }
143 | .Years {
144 | grid-area: YS;
145 | font-size: 14px;
146 | }
147 | p {
148 | font-size: 18px;
149 | }
150 | `
151 |
152 | class Demo extends Component {
153 | componentDidMount() {
154 | Prism.highlightAll();
155 | }
156 | render() {
157 | return (
158 |
159 |
160 |
D3 BLACKBOX
161 |
162 |
163 | Take any D3 example you find in the wild and wrap it in a
164 | React component. Great for quick experiments and meeting
165 | deadlines. 😛
166 |
175 | A{" "}
176 |
177 | barchart from D3
178 | {" "}
179 | docs wrapped in a React component. Takes 2 minutes to do
180 | from scratch with any code you find. 👇
181 |
182 |
183 |
194 |
195 |
198 |
199 |
200 |
Getting Started
201 |
202 | 1. Install
203 |
204 | {'d3blackbox'} and {'d3'}
205 |
206 |
207 |
208 |
209 | {'$ npm install d3blackbox'}
210 |
211 |
212 |
213 | 2. Wrap D3 code in {'D3blackbox'}
214 |
215 |
216 |
217 | {`
218 | import React from "react";
219 | import D3blackbox from "d3blackbox";
220 | import * as d3 from "d3";
221 | const Barchart = D3blackbox(function(anchor, props, state) {
222 | const svg = d3.select(anchor.current);
223 | // the rest of your D3 code
224 | });
225 | export default Barchart;`}
226 |
227 |
228 |
3. Render your component
229 |
230 |
231 | {`
232 |
233 | `}
234 |
235 |
236 |
237 |
240 |
241 |
242 |
Codesandbox
243 |
244 |
245 | Here's a Codesandbox example, if you prefer to play around.
246 |
247 |
248 |
259 |
260 |
261 |
262 |
Use different anchor component
263 |
264 | By default D3blackbox wraps your D3 render method in a
265 | <g> element. You can change that with the{" "}
266 | component prop.
267 |
268 |
269 | {`
270 |
271 | `}
272 |
273 |
Make sure your renderer knows how to handle that.
274 |
275 |
276 |
277 |
Use with React hooks
278 |
279 | React Hooks are still in alpha, but I know you're gonna ask.
280 | Yes, there's a hooks version useD3.
281 |