├── .babelrc
├── .gitignore
├── .npmignore
├── LICENSE.md
├── README.md
├── docs
├── App.js
├── AreaDemo.js
├── BarDemo.js
├── BarsLine.js
├── Controls.js
├── Debug.js
├── Dev.js
├── DoubleBars.js
├── Footer.js
├── Header.js
├── bundle.js
├── data.js
├── entry.js
├── index.html
├── styles.js
└── util.js
├── package.json
├── src
├── Area.js
├── Axis.js
├── Bar.js
├── Chart.js
├── DataLabels.js
├── Div.js
├── Dot.js
├── Dots.js
├── Label.js
├── Line.js
├── Rule.js
├── Rules.js
├── Svg.js
├── get-scale.js
├── index.js
└── withScale.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "stage-0",
5 | "react"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | dist
3 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 |
2 | docs
3 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 |
2 | # The MIT License (MIT)
3 |
4 | Copyright (c) 2016 Brent Jackson
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7 |
8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
9 |
10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # f0
3 |
4 | **WIP**
5 |
6 | Minimal, composable, fully-fluid SVG charts for React
7 |
8 | ## Getting Started
9 |
10 | ```sh
11 | npm i -S f0
12 | ```
13 |
14 | ```js
15 | import React from 'react'
16 | import { Line, Bars } from 'f0'
17 |
18 | const App = () => {
19 | const a = [
20 | 4, 8, 16, 32, 64
21 | ]
22 |
23 | const b = [
24 | 8, 2, 32, 16, 4
25 | ]
26 |
27 | return (
28 |
29 |
30 |
31 |
32 | )
33 | }
34 |
35 | export default App
36 | ```
37 |
38 | ## Features
39 |
40 | - Fully fluid charts with fixed heights
41 | - Uses HTML for labels to prevent scaling issues
42 | - Bare bones API - only accepts flat arrays as data
43 | - Low-level access to components for composition
44 |
45 | ## Components
46 |
47 | ### Line
48 |
49 | ```js
50 |
53 | ```
54 |
55 | ```js
56 |
62 | ```
63 |
64 | ### Area
65 |
66 | ```js
67 |
70 | ```
71 |
72 | ```js
73 |
78 | ```
79 |
80 | ### Bar
81 |
82 | ```js
83 |
86 | ```
87 |
88 | ```js
89 |
93 | ```
94 |
95 | ### Svg
96 |
97 | ```js
98 |
99 |
100 |
101 |
102 | ```
103 |
104 | ### Chart
105 |
106 | ```js
107 |
108 |
109 |
118 |
119 | ```
120 |
121 | ### Rules
122 |
123 | ```js
124 |
125 |
126 |
127 |
128 | ```
129 |
130 | ### Axis
131 |
132 | ```js
133 |
134 |
135 |
144 |
152 |
153 | ```
154 |
155 | ### DataLabels
156 |
157 | ```js
158 |
159 |
160 |
161 |
162 | ```
163 |
164 | ## Browser Support
165 |
166 | **Currently does not work in IE**
167 |
168 | The fluid style for these charts relies on SVG 1.2 vector-effect non-scaling-stroke.
169 | Modern evergreen browsers should support this feature, but charts may appear distorted in older browsers, including IE and Edge.
170 |
171 | [MIT License](/LICENSE.md)
172 |
173 |
--------------------------------------------------------------------------------
/docs/App.js:
--------------------------------------------------------------------------------
1 |
2 | // Dematerialization margin
3 |
4 | import React from 'react'
5 | import chroma from 'chroma-js'
6 | import Header from './Header'
7 | import BarDemo from './BarDemo'
8 | import DoubleBars from './DoubleBars'
9 | import BarsLine from './BarsLine'
10 | import AreaDemo from './AreaDemo'
11 | import Footer from './Footer'
12 |
13 | import Dev from './Dev'
14 | import Controls from './Controls'
15 |
16 | const blue = '#0077cc'
17 | const orange = '#ff5500'
18 | const colors = {
19 | blue,
20 | orange
21 | }
22 |
23 | const rand = () => Math.round(16 * Math.random())
24 |
25 | const getData = (length) => (
26 | Array.from({ length }).map(rand)
27 | )
28 |
29 | const getColors = () => {
30 | const [ h, s ] = chroma.random().hsl()
31 | const b0 = chroma.hsl([ h, s, 1 / 8 ]).hex()
32 | const b1 = chroma.hsl([ h, s, 1 / 4 ]).hex()
33 | const b2 = chroma.hsl([ h, s, 1 / 2 ]).hex()
34 | const b3 = chroma.hsl([ h, s, 3 / 4 ]).hex()
35 | const b4 = chroma.hsl([ h, s, 7 / 8 ]).hex()
36 |
37 | return [
38 | b0, b1, b2, b3, b4
39 | ]
40 | }
41 |
42 | class App extends React.Component {
43 | state = {
44 | count: 0,
45 | colors: getColors(),
46 | logo: getData(8),
47 | data: getData(8),
48 | neg: getData(8).map(n => -1 * n),
49 | xdata: [
50 | 3, 8, 4, 16,
51 | 48, 32, 24, 24
52 | ],
53 | xneg: [
54 | -48, -32, -24, -24,
55 | -3, -8, -4, -16
56 | ]
57 | }
58 |
59 | randomize = () => {
60 | const logo = this.state.logo.map(rand)
61 | const data = this.state.data.map(rand)
62 | const neg = this.state.data.map(rand).map(n => -1 * n)
63 | const colors = getColors()
64 | this.setState({
65 | logo,
66 | data,
67 | neg,
68 | colors
69 | })
70 | }
71 |
72 | onChange = obj => {
73 | this.setState(obj)
74 | }
75 |
76 | componentDidMount () {
77 | setInterval(this.randomize, 2000)
78 | }
79 |
80 | render () {
81 | const { data } = this.state
82 | const sx = {
83 | root: {
84 | fontFamily: '-apple-system, BlinkMacSystemFont, sans-serif',
85 | }
86 | }
87 |
88 | return (
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | {/*
97 |
98 |
99 | */}
100 |
101 | )
102 | }
103 | }
104 |
105 | export default App
106 |
107 |
--------------------------------------------------------------------------------
/docs/AreaDemo.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import { VictoryAnimation } from 'victory'
4 | import {
5 | Chart,
6 | Svg,
7 | Area
8 | } from '../src'
9 | import { fl } from './util'
10 |
11 | const AreaDemo = ({
12 | data,
13 | logo,
14 | colors
15 | }) => {
16 | const sx = {
17 | root: {
18 | color: colors[4],
19 | backgroundColor: colors[1],
20 | transitionProperty: 'color, background-color, fill',
21 | transitionDuration: '.2s, .8s',
22 | transitionTimingFunction: 'ease-out'
23 | },
24 | title: {
25 | display: 'flex',
26 | flexWrap: 'wrap',
27 | alignItems: 'flex-end'
28 | },
29 | space: {
30 | flex: '1 1 auto'
31 | }
32 | }
33 |
34 | return (
35 |
38 |
41 | {({ data, logo }) => (
42 |
43 |
44 | Area
45 |
46 |
47 |
Pattern buffer
48 |
{fl(data[data.length - 1])}
49 |
50 |
51 |
52 |
53 |
60 |
67 |
68 |
69 |
70 | )}
71 |
72 |
73 | )
74 | }
75 |
76 | export default AreaDemo
77 |
78 |
--------------------------------------------------------------------------------
/docs/BarDemo.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { VictoryAnimation } from 'victory'
3 | import {
4 | Chart,
5 | Svg,
6 | Bar,
7 | Rules,
8 | Axis
9 | } from '../src'
10 | import { fl } from './util'
11 |
12 | const BarDemo = ({
13 | data,
14 | colors
15 | }) => {
16 | const sx = {
17 | root: {
18 | color: colors[4],
19 | backgroundColor: colors[1],
20 | transitionProperty: 'color, background-color, fill',
21 | transitionDuration: '.2s, .8s',
22 | transitionTimingFunction: 'ease-out'
23 | },
24 | title: {
25 | display: 'flex',
26 | flexWrap: 'wrap',
27 | alignItems: 'flex-end'
28 | },
29 | space: {
30 | flex: '1 1 auto'
31 | }
32 | }
33 |
34 | return (
35 |
38 |
41 | {({ data }) => (
42 |
43 |
44 | Bar
45 |
46 |
47 |
Telemetry
48 |
{fl(data[data.length - 1])}
49 |
50 |
51 |
52 |
53 |
54 |
59 |
60 |
71 |
72 |
73 | )}
74 |
75 |
76 | )
77 | }
78 |
79 | export default BarDemo
80 |
81 |
--------------------------------------------------------------------------------
/docs/BarsLine.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import { VictoryAnimation } from 'victory'
4 | import {
5 | Svg,
6 | Bar,
7 | Line,
8 | Rules
9 | } from '../src'
10 | import { fl } from './util'
11 |
12 | const DoubleBars = ({
13 | data,
14 | neg,
15 | colors
16 | }) => {
17 | const sx = {
18 | root: {
19 | color: colors[4],
20 | backgroundColor: colors[0],
21 | transitionProperty: 'color, background-color, fill, stroke',
22 | transitionDuration: '.2s, .8s',
23 | transitionTimingFunction: 'ease-out'
24 | },
25 | title: {
26 | display: 'flex',
27 | flexWrap: 'wrap',
28 | alignItems: 'flex-end'
29 | },
30 | space: {
31 | flex: '1 1 auto'
32 | }
33 | }
34 |
35 | return (
36 |
39 |
42 | {({ data, neg }) => {
43 | const line = data.map((d, i) => d + neg[i])
44 |
45 | return (
46 |
47 |
48 | Bar + Line
49 |
50 |
51 |
Tachyonic inversion
52 |
{fl(line[line.length - 1])}
53 |
54 |
55 |
56 |
63 |
72 |
82 |
83 |
84 | )
85 | }}
86 |
87 |
88 | )
89 | }
90 |
91 | export default DoubleBars
92 |
93 |
--------------------------------------------------------------------------------
/docs/Controls.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import {
4 | Slider
5 | } from 'rebass'
6 |
7 | const Controls = props => {
8 | const {
9 | data,
10 | neg,
11 | logo,
12 | onChange
13 | } = props
14 |
15 | const handleNumberChange = i => e => {
16 | const { name, value } = e.target
17 | const arr = props[name]
18 | const num = parseFloat(value)
19 | arr[i] = num
20 | onChange({ [name]: arr })
21 | }
22 |
23 | return (
24 |
25 | {neg.map((d, i) => (
26 |
35 | ))}
36 |
37 | {data.map((d, i) => (
38 |
46 | ))}
47 |
48 | )
49 | }
50 |
51 | export default Controls
52 |
53 |
--------------------------------------------------------------------------------
/docs/Debug.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 |
4 | const Debug = props => (
5 | {JSON.stringify(props, null, 2)}
6 | )
7 |
8 | export default Debug
9 |
10 |
--------------------------------------------------------------------------------
/docs/Dev.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import {
4 | VictoryAnimation,
5 | VictoryAxis
6 | } from 'victory'
7 | import {
8 | Chart,
9 | Svg,
10 | Line,
11 | Area,
12 | Bar,
13 | Rules,
14 |
15 | Axis,
16 | DataLabels,
17 |
18 | XAxis,
19 | Label,
20 |
21 | Labels
22 | } from '../src'
23 | import { fl } from './util'
24 |
25 | const Dev = ({
26 | data,
27 | neg,
28 | logo,
29 | colors
30 | }) => {
31 | const sx = {
32 | root: {
33 | padding: 48
34 | },
35 | bars: {
36 | marginBottom: 32,
37 | transitionProperty: 'fill, stroke',
38 | transitionDuration: '.4s',
39 | transitionTimingFunction: 'ease-out'
40 | }
41 | }
42 |
43 | return (
44 |
45 |
Dev
46 |
53 | {({ data, logo }) => (
54 |
55 |
56 |
57 |
64 |
73 |
74 |
75 |
76 |
79 |
87 |
88 |
89 |
96 |
97 |
103 |
104 |
115 |
122 |
123 | )}
124 |
125 |
{JSON.stringify(data, null, 2)}
126 |
{JSON.stringify(neg, null, 2)}
127 |
{JSON.stringify(logo, null, 2)}
128 |
{JSON.stringify(colors, null, 2)}
129 |
130 | )
131 | }
132 |
133 | export default Dev
134 |
--------------------------------------------------------------------------------
/docs/DoubleBars.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import { VictoryAnimation } from 'victory'
4 | import {
5 | Svg,
6 | Bar,
7 | Rules
8 | } from '../src'
9 | import { fl } from './util'
10 |
11 | const DoubleBars = ({
12 | data,
13 | neg,
14 | colors
15 | }) => {
16 | const sx = {
17 | root: {
18 | color: '#fff',
19 | backgroundColor: colors[3],
20 | transitionProperty: 'color, background-color, fill',
21 | transitionDuration: '.2s, .8s',
22 | transitionTimingFunction: 'ease-out'
23 | },
24 | title: {
25 | display: 'flex',
26 | flexWrap: 'wrap',
27 | alignItems: 'flex-end'
28 | },
29 | space: {
30 | flex: '1 1 auto'
31 | }
32 | }
33 | return (
34 |
37 |
40 | {({ data, neg }) => (
41 |
42 |
43 | Bar
44 |
45 |
46 |
Time dilation
47 |
{fl(data[data.length - 1])}
48 |
49 |
50 |
51 |
52 |
59 |
68 |
69 |
70 | )}
71 |
72 |
73 | )
74 | }
75 |
76 | export default DoubleBars
77 |
78 |
--------------------------------------------------------------------------------
/docs/Footer.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 |
4 | const Footer = ({
5 | colors
6 | }) => {
7 | const sx = {
8 | root: {
9 | color: colors[4],
10 | backgroundColor: colors[0],
11 | }
12 | }
13 |
14 | return (
15 |
21 | )
22 | }
23 |
24 | export default Footer
25 |
26 |
--------------------------------------------------------------------------------
/docs/Header.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import { VictoryAnimation } from 'victory'
4 | import {
5 | Line,
6 | Svg,
7 | Rules
8 | } from '../src'
9 | import { format } from 'd3-format'
10 |
11 | const fl = format('.2f')
12 |
13 | const Header = ({ logo, colors }) => {
14 | const sx = {
15 | root: {
16 | color: colors[4],
17 | backgroundColor: colors[2],
18 | transitionProperty: 'color, background-color, fill, stroke',
19 | transitionDuration: '.2s, .8s',
20 | transitionTimingFunction: 'ease-out'
21 | },
22 | title: {
23 | display: 'flex',
24 | flexWrap: 'wrap',
25 | alignItems: 'flex-end',
26 | color: colors[1] // styles.b[1]
27 | },
28 | space: {
29 | flex: '1 1 auto'
30 | }
31 | }
32 |
33 | return (
34 |
75 | )
76 | }
77 |
78 | export default Header
79 |
80 |
--------------------------------------------------------------------------------
/docs/data.js:
--------------------------------------------------------------------------------
1 |
2 | import pkg from '../package.json'
3 |
4 | const data = {
5 | ...pkg
6 | }
7 |
8 | export default data
9 |
10 |
--------------------------------------------------------------------------------
/docs/entry.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import ReactDOM from 'react-dom'
4 | import App from './App'
5 |
6 | ReactDOM.render( , app)
7 |
8 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | f0
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
14 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/docs/styles.js:
--------------------------------------------------------------------------------
1 |
2 | const colors = {
3 | cyan: '#02ffd5',
4 | darkcyan: '#104a40',
5 | black: '#041210',
6 | }
7 |
8 | const b = [
9 | '#001221',
10 | '#003561',
11 | '#0059a0',
12 | '#007ce0',
13 | '#219cff',
14 | '#61b8ff',
15 | '#a0d5ff',
16 | ]
17 |
18 | const styles = {
19 | colors,
20 | b,
21 | c: [
22 | '#041210',
23 | '#104a40',
24 | '#01bf9f',
25 | '#02ffd5',
26 | ]
27 | }
28 |
29 | export default styles
30 |
31 |
--------------------------------------------------------------------------------
/docs/util.js:
--------------------------------------------------------------------------------
1 |
2 | import { format } from 'd3-format'
3 |
4 | export const fl = format('.2f')
5 |
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "f0",
3 | "version": "1.0.0-b4",
4 | "description": "Minimal, composable, fully-fluid SVG charts for React",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "prepublish": "mkdir -p dist && babel src --out-dir dist",
8 | "start": "webpack-dev-server",
9 | "build": "webpack -p"
10 | },
11 | "keywords": [
12 | "charts",
13 | "react",
14 | "svg",
15 | "responsive",
16 | "fluid"
17 | ],
18 | "author": "Brent Jackson",
19 | "license": "MIT",
20 | "devDependencies": {
21 | "babel-cli": "^6.16.0",
22 | "babel-core": "^6.17.0",
23 | "babel-loader": "^6.2.5",
24 | "babel-preset-es2015": "^6.16.0",
25 | "babel-preset-react": "^6.16.0",
26 | "babel-preset-stage-0": "^6.16.0",
27 | "chroma-js": "^1.2.1",
28 | "d3-format": "^1.0.2",
29 | "json-loader": "^0.5.4",
30 | "open-color": "^1.4.0",
31 | "react": "^15.3.2",
32 | "react-dom": "^15.3.2",
33 | "react-motion": "^0.4.5",
34 | "react-tween-state": "^0.1.5",
35 | "rebass": "^0.4.0-beta.8",
36 | "victory": "^0.13.0",
37 | "webpack": "^1.13.2",
38 | "webpack-dev-server": "^1.16.2"
39 | },
40 | "dependencies": {
41 | "d3-array": "^1.0.1",
42 | "d3-scale": "^1.0.3"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Area.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import withScale from './withScale'
4 | import Svg from './Svg'
5 |
6 | const Area = ({
7 | points = [],
8 | color = 'currentcolor',
9 | opacity = 1,
10 | padWidth,
11 | ...props
12 | }) => {
13 | if (!points.length) return null
14 |
15 | const command = i => i === 0 ? 'M' : 'L'
16 |
17 | const d = [
18 | ...points.map(({ x, y }, i) => (
19 | `${command(i)} ${x} ${y}`
20 | )),
21 | `V100 H0 z`
22 | ]
23 |
24 | return (
25 |
26 |
31 |
32 | )
33 | }
34 |
35 | export default withScale(Area)
36 |
37 |
--------------------------------------------------------------------------------
/src/Axis.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import getScale from './get-scale'
4 | import Div from './Div'
5 | import Label from './Label'
6 |
7 | // Split up or handle unpadded labels
8 |
9 | const XAxis = ({
10 | labels = [],
11 | rule,
12 | scale,
13 | padWidth,
14 | style
15 | }) => {
16 | const sx = {
17 | root: {
18 | display: 'flex',
19 | justifyContent: 'space-between',
20 | alignItems: 'flex-start',
21 | position: 'relative',
22 | minHeight: 32,
23 | marginTop: rule ? -1 : null,
24 | borderTop: rule ? '1px solid' : null,
25 | ...style
26 | },
27 | label: {
28 | textAlign: 'center',
29 | flexBasis: padWidth + '%',
30 | paddingTop: 8,
31 | paddingBottom: 8,
32 | }
33 | }
34 |
35 | return (
36 |
37 | {labels.map((label, i) => {
38 | const x = scale.padx(i)
39 | return (
40 |
45 | )
46 | })}
47 |
48 | )
49 | }
50 |
51 | const YAxis = ({
52 | labels = [],
53 | rule,
54 | ...style
55 | }) => {
56 | const sx = {
57 | root: {
58 | position: 'absolute',
59 | top: 0,
60 | left: 0,
61 | bottom: 0,
62 | borderLeft: rule ? '1px solid' : null,
63 | ...style
64 | },
65 | label: {
66 | paddingLeft: rule ? 8 : null
67 | }
68 | }
69 | return (
70 |
71 | {labels.map((label, i) => (
72 |
75 |
78 |
79 | ))}
80 |
81 | )
82 | }
83 |
84 | const Axis = ({
85 | labels = [],
86 | length,
87 | x,
88 | y,
89 | ...props
90 | }) => {
91 | const { scale, padWidth } = getScale({ length: length || labels.length })
92 |
93 | if (y) {
94 | return (
95 |
99 | )
100 | }
101 |
102 | return (
103 |
109 | )
110 | }
111 |
112 | export default Axis
113 |
114 |
--------------------------------------------------------------------------------
/src/Bar.js:
--------------------------------------------------------------------------------
1 |
2 | // Rename to Bar
3 |
4 | import React from 'react'
5 | import withScale from './withScale'
6 | import Svg from './Svg'
7 |
8 | const Bar = ({
9 | scale,
10 | padWidth,
11 | points = [],
12 | color = 'currentcolor',
13 | ...props
14 | }) => {
15 | if (!points.length) return null
16 |
17 | const bars = points.map(({ padx, y, d }, i) => {
18 | const bx = padx - padWidth / 2
19 | const hi = d === 0
20 | ? 0
21 | : d > 0 ? 100 - y : y
22 | const by = d >= 0 ? y : 0
23 |
24 | return (
25 |
32 | )
33 | })
34 |
35 | return (
36 |
41 | )
42 | }
43 |
44 | export default withScale(Bar)
45 |
46 |
--------------------------------------------------------------------------------
/src/Chart.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 |
4 | const Chart = ({
5 | width = '100%',
6 | height = 256,
7 | style,
8 | ...props
9 | }) => {
10 | const sx = {
11 | position: 'relative',
12 | width,
13 | height,
14 | // minHeight: height,
15 | ...style
16 | }
17 |
18 | return (
19 |
22 | )
23 | }
24 |
25 | export default Chart
26 |
27 |
--------------------------------------------------------------------------------
/src/DataLabels.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import withScale from './withScale'
4 | import Div from './Div'
5 | import Label from './Label'
6 |
7 | const DataLabels = ({
8 | scale,
9 | padWidth,
10 | pad,
11 | points = [],
12 | min,
13 | max,
14 | format = n => n,
15 | ...props
16 | }) => {
17 | const sx = {
18 | root: {},
19 | div: {
20 | width: padWidth + '%'
21 | },
22 | label: {
23 | width: '100%',
24 | textAlign: 'center',
25 | }
26 | }
27 |
28 | const labels = points.map(({ x, padx, y, d }, i) => {
29 | const lx = padx - padWidth / 2
30 | const ly = y
31 | return (
32 |
35 |
39 |
40 | )
41 | })
42 |
43 | return (
44 |
45 | {labels}
46 |
47 | )
48 | }
49 |
50 | export default withScale(DataLabels)
51 |
52 |
--------------------------------------------------------------------------------
/src/Div.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 |
4 | const Div = ({
5 | x = 0,
6 | y = 0,
7 | top,
8 | style,
9 | center,
10 | ...props
11 | }) => {
12 | const transform = center && top
13 | ? 'translate(-50%, -100%)'
14 | : center
15 | ? 'translate(-50%, 0)'
16 | : top
17 | ? 'translate(0, -100%)'
18 | : null
19 |
20 | const sx = {
21 | position: 'absolute',
22 | top: y + '%',
23 | left: x + '%',
24 | transform,
25 | ...style
26 | }
27 |
28 | return (
29 |
33 | )
34 | }
35 |
36 | export default Div
37 |
38 |
--------------------------------------------------------------------------------
/src/Dot.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 |
4 | const Dot = ({
5 | x = 0,
6 | y = 0,
7 | size = 12,
8 | color = 'currentcolor',
9 | fill,
10 | strokeWidth,
11 | style
12 | }) => {
13 | return (
14 |
15 |
26 | {fill && (
27 |
38 | )}
39 |
40 | )
41 | }
42 |
43 | export default Dot
44 |
45 |
--------------------------------------------------------------------------------
/src/Dots.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import Dot from './Dot'
4 |
5 | const Dots = ({
6 | points = [],
7 | color,
8 | size,
9 | fill,
10 | strokeWidth,
11 | style
12 | }) => {
13 | return (
14 |
15 | {points.map(({ x, y }, i) => (
16 |
26 | ))}
27 |
28 | )
29 | }
30 |
31 | export default Dots
32 |
33 |
--------------------------------------------------------------------------------
/src/Label.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 |
4 | const Label = ({
5 | style,
6 | ...props
7 | }) => {
8 | const sx = {
9 | fontSize: 12,
10 | minHeight: 16,
11 | ...style
12 | }
13 |
14 | return (
15 |
16 | )
17 | }
18 |
19 | export default Label
20 |
21 |
--------------------------------------------------------------------------------
/src/Line.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import withScale from './withScale'
4 | import Svg from './Svg'
5 | import Dots from './Dots'
6 |
7 | const isNum = n => !isNaN(n)
8 |
9 | const Line = ({
10 | scale,
11 | points = [],
12 | min,
13 | max,
14 | pad,
15 | padWidth,
16 |
17 | color = 'currentcolor',
18 | strokeWidth = 3,
19 | strokeLinecap = 'round',
20 |
21 | // Should this be an object?
22 | dots,
23 | dotSize,
24 | dotColor,
25 | dotFill,
26 | style = {},
27 | children,
28 | ...rest
29 | }) => {
30 | if (!points.length) return null
31 |
32 | const command = i => i === 0 ? 'M' : 'L'
33 |
34 | // Remove after removing Dots
35 | const p = points.map(p => pad ? ({ ...p, x: p.padx }) : p)
36 |
37 | const d = points.map(({ x, padx, y }, i) => {
38 | const lx = pad ? padx : x
39 | return `${command(i)} ${lx} ${y}`
40 | })
41 |
42 | return (
43 |
44 |
52 | {dots && (
53 |
60 | )}
61 |
62 | )
63 | }
64 |
65 | export default withScale(Line)
66 |
67 |
--------------------------------------------------------------------------------
/src/Rule.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 |
4 | const isNum = n => n !== null && !isNaN(n)
5 |
6 | const Rule = ({
7 | x,
8 | y,
9 | strokeWidth = 1,
10 | color = 'currentcolor',
11 | opacity = 1 / 4
12 | }) => {
13 | if (!isNum(x) && !isNum(y)) return null
14 |
15 | const d = isNum(x)
16 | ? `M${x} 0 V100`
17 | : `M0 ${y} H100`
18 |
19 | return (
20 |
28 | )
29 | }
30 |
31 | export default Rule
32 |
33 |
--------------------------------------------------------------------------------
/src/Rules.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import Svg from './Svg'
4 | import Rule from './Rule'
5 | import getScale from './get-scale'
6 |
7 | const Rules = ({
8 | x = 0,
9 | y,
10 | pad,
11 | strokeWidth = 1,
12 | color,
13 | opacity,
14 | ...props
15 | }) => {
16 | if (x < 1 && y < 1) return null
17 |
18 | const viewBox = [ 0, 0, 100, 100 ].join(' ')
19 |
20 | const { scale } = getScale({ length: x })
21 | const scalex = pad ? scale.padx : scale.x
22 |
23 | const xrules = Array.from({ length: x })
24 | .map((n, i) => i)
25 | .map(n => (
26 |
33 | ))
34 |
35 | const yrules = Array.from({ length: y })
36 | .map((n, i) => i)
37 | .map(n => {
38 | const step = n / (y - 1) * 100
39 | return (
40 |
47 | )
48 | })
49 |
50 | return (
51 |
52 | {xrules}
53 | {yrules}
54 |
55 | )
56 | }
57 |
58 | export default Rules
59 |
60 |
--------------------------------------------------------------------------------
/src/Svg.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 |
4 | const Svg = ({
5 | viewBox = '0 0 100 100',
6 | width = '100%',
7 | height = 256,
8 | style,
9 | ...props
10 | }) => {
11 | const sx = {
12 | position: 'relative',
13 | display: 'block',
14 | margin: 0,
15 | overflow: 'visible',
16 | width,
17 | height,
18 | ...style
19 | }
20 |
21 | return (
22 |
28 | )
29 | }
30 |
31 | export default Svg
32 |
33 |
--------------------------------------------------------------------------------
/src/get-scale.js:
--------------------------------------------------------------------------------
1 |
2 | import { scaleLinear } from 'd3-scale'
3 | import {
4 | min as d3min,
5 | max as d3max
6 | } from 'd3-array'
7 |
8 | const isNum = n => !isNaN(n)
9 |
10 | const getScale = ({
11 | data = [],
12 | length, // Optional length
13 | min, // optional min value
14 | max, // optional max value
15 | padRatio = 2, // Ratio of bar width vs margin
16 | }) => {
17 | const datamin = d3min(data)
18 | const datamax = d3max(data)
19 |
20 | min = isNum(min) && min <= datamin ? min : datamin || 0
21 | max = isNum(max) && max >= datamax ? max : datamax || 100
22 | length = length || data.length
23 |
24 | const denominator = length * (padRatio + 1)
25 | const pad = 1 / denominator * 100
26 | const padWidth = padRatio / denominator * 100
27 |
28 | const xdomain = ([ 0, length - 1 ])
29 | const ydomain = ([ min, max ])
30 | const x = scaleLinear().domain(xdomain).range([ 0, 100 ])
31 | const padx = scaleLinear().domain(xdomain).range([ padWidth / 2, 100 - padWidth / 2 ])
32 | const y = scaleLinear().domain(ydomain).range([ 100, 0 ])
33 |
34 | const scale = { x, padx, y }
35 |
36 | return {
37 | min,
38 | max,
39 | scale,
40 | pad,
41 | padWidth
42 | }
43 | }
44 |
45 | export default getScale
46 |
47 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 |
2 | // SVG
3 | import Svg from './Svg'
4 | import Line from './Line'
5 | import Area from './Area'
6 | import Bar from './Bar'
7 | import Rules from './Rules'
8 |
9 | // HTML
10 | import Chart from './Chart'
11 | import Axis from './Axis'
12 | import DataLabels from './DataLabels'
13 |
14 | // HOC
15 | import getScale from './get-scale'
16 | import withScale from './withScale'
17 |
18 | export { default as Svg } from './Svg'
19 | export { default as Line } from './Line'
20 | export { default as Area } from './Area'
21 | export { default as Bar } from './Bar'
22 | export { default as Rules } from './Rules'
23 |
24 | export { default as Chart } from './Chart'
25 | export { default as Axis } from './Axis'
26 | export { default as DataLabels } from './DataLabels'
27 |
28 | export { default as getScale } from './get-scale'
29 | export { default as withScale } from './withScale'
30 |
31 | export default {
32 | Svg,
33 | Line,
34 | Area,
35 | Bar,
36 | Chart,
37 | Axis,
38 | Rules,
39 | DataLabels,
40 | getScale,
41 | withScale
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/src/withScale.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react'
3 | import getScale from './get-scale'
4 |
5 | const withScale = Comp => {
6 | const ScaledComp = ({
7 | data = [],
8 | viewBox,
9 | ...props
10 | }) => {
11 | viewBox = viewBox || [ 0, 0, 100, 100 ].join(' ')
12 |
13 | const {
14 | min,
15 | max,
16 | scale,
17 | padWidth
18 | } = getScale({ data, ...props })
19 |
20 | const points = data.map((d, i) => {
21 | return {
22 | d,
23 | x: scale.x(i),
24 | padx: scale.padx(i),
25 | y: scale.y(d)
26 | }
27 | })
28 |
29 | return (
30 |
40 | )
41 | }
42 |
43 | return ScaledComp
44 | }
45 |
46 | export default withScale
47 |
48 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 |
2 | const path = require('path')
3 |
4 | module.exports = {
5 | entry: './docs/entry.js',
6 |
7 | output: {
8 | path: path.join(__dirname, 'docs'),
9 | filename: 'bundle.js'
10 | },
11 |
12 | module: {
13 | loaders: [
14 | {
15 | test: /\.js$/,
16 | exclude: /node_modules/,
17 | loader: 'babel'
18 | },
19 | {
20 | test: /\.json$/,
21 | loader: 'json'
22 | }
23 | ]
24 | },
25 |
26 | devServer: {
27 | contentBase: 'docs/'
28 | }
29 | }
30 |
31 |
--------------------------------------------------------------------------------