├── .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 | hero 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 |

167 | Source code on GitHub 168 | 169 |
170 | 171 | 172 |

Basic usage

173 | 174 |

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 |