olical / react-faux-dom Goto Github PK
View Code? Open in Web Editor NEWDOM like structure that renders to React (unmaintained, archived)
Home Page: http://oli.me.uk/2015/09/09/d3-within-react-the-right-way/
License: The Unlicense
DOM like structure that renders to React (unmaintained, archived)
Home Page: http://oli.me.uk/2015/09/09/d3-within-react-the-right-way/
License: The Unlicense
I think this might need to be bound, i.e. change line to
setTimeout(this.drawFauxDOM.bind(this))
Thanks for the lib.
Consider;
/*.. imports ..*/
class myClass extends React.Component {
render( ) {
this.container = ReactFauxDOM.createElement( 'svg' );
return { this.rendition.toReact() }
}
rendition(){
var myRect = d3.select( this.container ).append( 'rect' );
ReactFauxDOM.toRef( myRect, 'rect' );
}
componentDidMount() {
console.log( this.refs.rect )
}
}
is it logical/sound from your perspective to provide a means such as the above so as to gain ReactJS refs from the faux dom when toReact is called?
I'm trying to recreate a simple JSFiddle: http://jsfiddle.net/am8ZB/
Below is my component, I used the same code in render, but with FauxDOM for creating the svg.
No luck with using png files either.
import React from 'react'
import * as d3 from 'd3'
import ReactFauxDOM from 'react-faux-dom'
class D3SLDiagram extends React.Component {
constructor(props) {
super(props)
this.state = {}
}
render () {
var width = 800, height = 800;
var svg = d3.select(ReactFauxDOM.createElement('svg'))
.attr("width", width)
.attr("height", height);
var g = svg.append("g");
var img = g.append("svg:image")
.attr("xlink:href", "http://www.clker.com/cliparts/1/4/5/a/1331068897296558865Sitting%20Racoon.svg")
.attr("width", 200)
.attr("height", 200)
.attr("x", 228)
.attr("y",53);
return (
<div className='network D3SLDiagram'>
{svg.node().toReact()}
</div>
)
}
}
export default D3SLDiagram
I'm totally stuck, any help would be appreciated, thanks!
Is there a way to use the mixins in an ES6 class if not is there an update coming that works with ES6 classes.
According to the documentation, there are several ways to set children to a ReactFauxDOM.Element
, also, children can be either a ReactFauxDOM.Element
or a React.Component
. Yet, this isn't working for me.
I'm using D3 (v4) to visualise a force layout of nodes, next to D3, I'm also using react-bootstrap as they have some handy components for Tooltips. For such a tooltip to work, you need to specify the react-bootstrap
component as the parent of the component you'd like a tooltip over. As such, I need a way to insert children in the ReactFauxDOM
structure, in order to set their children.
Here's the code I have so far, that I know works:
const faux_svg = new ReactFauxDOM.Element('svg')
faux_svg.appendChild(new ReactFauxDOM.Element('g'))
This generates something like this:
<svg>
<g></g>
</svg>
Now, in stead of a <g></g>
element, I need this react-bootstrap
component:
const faux_svg = new ReactFauxDOM.Element('svg')
faux_svg.appendChild(React.createElement('OverlayTrigger'))
This doesn't work, no errors are thrown, it just doesn't work.
Some alternatives I tried:
faux_svg.appendChild(React.createElement('g'))
faux_svg.appendChild(<OverlayTrigger />)
const ot = <OverlayTrigger />
faux_svg.appendChild(ot)
const ot = new ReactFauxDOM.Element('g', faux_svg) // even calling children this way, doesn't work
I'm not sure what I'm doing wrong here. Hope someone can point me in the right direction.
PS: Love the library!
Dear @Olical
I am using react-faux-dom now to reproduce a bar chart.
chart.selectAll(".bar")
.data(data)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.tradeDate.slice(5)); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.turnoverVol); })
.attr("height", function(d) { return height - y(d.turnoverVol); })
.on("click", function(d, i) {console.log(d);console.log(i)});
when I click, "d" shows undefined, "i" shows correctly. I have tried console.log(d) in other attr, they all work well.
Am I wrong with something? How to use new event system? Thank you very much!
I'm working on a couple components using react-faux-dom
. It literally just works out the box thank you for this. As it stands I have a line chart, bar chart, area chart, scatter-plot etc. However all of these each come with their own x/y-axis, grid, legend. I don't want to duplicate the code, as it makes more sense to abstract these out of the different charts into their own components. I'm not exactly sure how to go about composing the x/y-axis, legend, and the chart chosen together.
Some code may make more sense:
// What I have in my head
<XYAxis grid={true}>
<LineChart {...}/>
<Legend {...} />
</XYAxis>
<XYAxis grid={false}>
<BarChart {...}/>
<Legend {...} />
</XYAxis>
I'm not sure exactly how to go about this in react-dom-faux
, if you've done something like this with your library I'd like to know what approach might have worked best for you.
I'd also like to add Animations using React Motion, for things like updates in the data set, filtering data, tool-tips, etc. What would you suggest be the best approach for adding animations with 'react-faux-dom'?
I wouldn't mind adding some more example docs for your library as It has really made my life easier. If theres anything specific you want to add but haven't had the time let me know ๐๐ฝ
Hi there, thanks for creating this great package. I was hoping you could point out what I could be doing wrong?
My goal is to change the fill color of the bars on 'mouseover' and back to normal on 'mouseout' events. I am following this simple example http://bost.ocks.org/mike/bar/2/#automatic. The only changes I've made is adding the event listeners and creating an element with the ReactFauxDOM.
The chart is rendering and the events are being triggered, as it is logging to the console, but the color is not changing. I feel like there is probably a mistake in my implementation though I'm not sure? Is there something I'm missing?
Any advice would be greatly appreciated. Thanks and keep up the great work!
import React from 'react';
import ReactFauxDOM from 'react-faux-dom';
import d3 from 'd3';
class TestChart extends React.Component {
render() {
var data = [4, 8, 15, 16, 23, 42];
var width = 420;
var barHeight = 20;
var x = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, width]);
var chart = d3.select(ReactFauxDOM.createElement('svg'))
.attr('width', width)
.attr('height', barHeight * data.length);
var bar = chart.selectAll('g')
.data(data)
.enter().append('g')
.attr('transform', function(d, i) { return 'translate(0,' + i * barHeight + ')'; });
bar.append('rect')
.attr('width', x)
.attr('height', barHeight - 1)
.on('mouseover', function(d) {
d3.select(this).style('fill', 'green');
console.log('over');
})
.on('mouseout', function(d) {
d3.select(this).style('fill', 'steelblue');
console.log('out');
});
bar.append('text')
.attr('x', function(d) { return x(d) - 3; })
.attr('y', barHeight / 2)
.attr('dy', '.35em')
.text(function(d) { return d; });
return chart.node().toReact();
}
};
export default TestChart;
Dear @Olical
Glad to see this progress of the integration between D3 and React. I ever intended to use dc.js and react.js for analyzing data of stocks. When I want to show changes of data, animation does matter. In your approach, how to show animation? I suggest React-Motion which is a promising package.
https://github.com/dc-js/dc.js
https://github.com/chenglou/react-motion
how to generate jsx in D3 manner? Maybe something like:
// Create your element.
var el = ReactFauxDOM.createElement('div')
// Change stuff using actual DOM functions.
// Even perform CSS selections.
el.style.setProperty('color', 'red')
el.setAttribute('class', 'box')
el.Spring.setDefaultValue({val: 0})
el.Spring.setEndValue({val: this.state.open? 400 : 0})
// Render it to React elements.
return el.toReact()
// Yields:
<Spring defaultValue={{val: 0}} endValue={{val: this.state.open ? 400 : 0}}>
{interpolated =>
<div className='box' style={{
transform: `translate3d(${interpolated.val}px, 0, 0)`,
color: 'red'
}} ></div>
}
</Spring>
Hey, just opened sourced a library that uses react-faux-dom
, check it out ๐
component-kit
Also theres a link there to show you a dashboard.
"peerDependencies": {
"react": "0.x"
}
This doesn't match with 0.14.0-rc1
since npm doesn't versions containing [a-Z] characters as releases. (as long as I can remember)
npm ERR! node v0.12.2
npm ERR! npm v2.7.4
npm ERR! code EPEERINVALID
npm ERR! peerinvalid The package react does not satisfy its siblings' peerDependencies requirements!
npm ERR! peerinvalid Peer [email protected] wants react@^0.14.0-rc1
npm ERR! peerinvalid Peer [email protected] wants react@>=0.13.3 || ^0.14.0-beta3 || ^0.14.0-rc1
npm ERR! peerinvalid Peer [email protected] wants react@>=0.13.2 || ^0.14.0-rc1
npm ERR! peerinvalid Peer [email protected] wants [email protected]
Basic D3 example (not using react-faux-dom
)
// #test is: <svg id="test"></svg>
const svg = d3.select("#test")
.attr({width, height})
.append("circle")
.attr("r", 4.5)
.attr("cx", 60)
.attr("cy", 60)
.style("fill", "steelblue");
This works of course. This however, does not:
const svg = d3.select(ReactFauxDOM.createElement('svg'))
.attr({width, height})
.append("circle")
.attr("r", 4.5)
.attr("cx", 60)
.attr("cy", 60)
.style("fill", "steelblue");
return svg.node().toReact();
I think the reason for this is that append does not correctly chain. What should happen is that the append
acts like a selector and the following attr
operates on the circle
, but that is not what happens, they operate on the svg
.
This works fine:
const svg = d3.select(ReactFauxDOM.createElement('svg'))
.attr({width, height});
svg.append("circle")
.attr("r", 4.5)
.attr("cx", 60)
.attr("cy", 60)
.style("fill", "steelblue");
return svg.node().toReact();
I am not experienced with D3, but it would seem to me that usage of react-faux-dom is the only difference between the examples so I guess the problem lies there.
Cheers,
Douglas
Right now styles default to undefined. Defaulting to strings should fix a few things mentioned at the bottom of #45. Any input on this would be cool.
Not sure if this should be in the readme or another file. But a small script that generates a list of contributors would be cool upon release. Contributor list in each version in CHANGES.md
? Sort by amount of commits or last to commit? Or maybe by size of change or amount of branches merged?
I've seen it done on qutebrowser where it was sorted by commits, but I commit a lot, so it may not be representative. Maybe I'll count PRs merged? :)
Thoughts? I feel like it'll be nice to credit people in some way. Contributions and discussions about this project make me happy, so I'd like to encourage it.
Version: latest react-faux-dom (2.7.1) and React 15.1.0.
When I do:
const node = ReactFauxDOM.createElement('svg');
const svg = d3.select(node)
.attr('width', width + margin.left + margin.right)
.attr('height', h + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
...
svg.append('rect')
.attr('width', width)
.attr('height', h)
.style('fill', 'none')
.style('pointer-events', 'all')
.on('mousemove', function() {
const pos = d3.mouse(this);
console.log(pos);
});
pos
is always [NaN, NaN]
.
I can confirm that this
inside the mousemove
callback is a DOM element (e.g. Element {nodeName: "rect", parentNode: Element, childNodes: Array[0], eventListeners: Object, text: ""โฆ}
).
Any ideas why?
As pointed out in #4, vendor prefixed styles appear to be camel cased incorrectly. This need verifying.
import './style.less';
import React from 'react';
import d3 from 'd3';
import ReactFauxDOM from 'react-faux-dom';
const Component = React.createClass({
render: function () {
const svg = ReactFauxDOM.createElement('svg');
d3.select(svg)
.attr({
width: 300,
height: 300
})
.append('rect')
.attr({
width: 300,
height: 300,
fill: 'green'
});
return svg.toReact()
}
});
export default Component;
Hi @Olical, I was thinking about raising a pull-request for D3 to prevent it using the window
or document
globals, and instead using code like node.ownerDocument
and node.ownerDocument.defaultView
, but this just seems wrong since it's perfectly valid for D3 to do that, and it shouldn't have to start using awkward syntax for the sake of 'react-faux-dom'.
Instead, it seems like the correct thing to do would be to add an install()
method to 'react-faux-dom' that updated these global objects so that they behave correctly. Apps could then invoke this method before using D3, or any libraries that use D3.
What do you think? Does this make sense, and if so should I try to create a pull-request for this?
I'm a bit surprised that the faux DOM createElement method doesn't support the same method signature as regular React.createElement:
ReactElement.createElement = function (type, config, children) {
ReactFauxDOM:
createElement: function (nodeName) {
As a result, it appears it isn't possible to actually swap ReactFauxDOM in for React, and use JSX:
render () {
var _React = React;
React = ReactFauxDOM;
var result = (
<div className="ViewsHistogram box">
Hello
</div>
);
React = _React;
return result.toReact();
}
(The className
and Hello
text are ignored by ReactFauxDOM).
Is there a reason for this? Why not use the same method signature, and get JSX support for free?
Right now, as per normal React, you have to set the style with an object. I've created the style.{set,get,remove}Property
methods, but it would be cool to parse style strings into objects if you set element.style = 'foo: bar;'
.
Easily done with getters and setters. Because style is always a string in the real DOM, setting and getting it should encode and decode the string to and from an object.
To reproduce, select a react-faux-dom div
with D3, append an svg
, and then call a behavior, e.g.:
import d3 from 'd3';
import ReactDOM from 'react-dom';
import { createElement } from 'react-faux-dom';
const target = createElement('div');
d3.select(target).append('svg').call(d3.behavior.drag());
ReactDOM.render(target.toReact(), document.getElementById('render-target'));
The browser throws an error when you click/drag the svg
element:
d3.js: Uncaught TypeError: x.getBoundingClientRect is not a function
Similar code using a real DOM element throws no error:
const target = document.createElement('div');
d3.select(target).append('svg').call(d3.behavior.drag());
document.getElementById('render-target').appendChild(target);
The desired behavior is that the fake element supports getBoundingClientRect
, like a real DOM element.
#23 added some cool support for data-*
etc but it instantiates the RegExp every time, we should reuse those.
I'm trying to use with nvd3, and I get an error "TypeError: Cannot read property 'replace' of undefined(โฆ)" on the line var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""), 10);
I can provide code if needed
This will allow you to embed Spring
animations as mentioned in #2 for example. Although spring requires you to set a child to a function I think, so that should be taken into account.
Basically let the user pass things other than strings to createElement that will be rendered when the tree is converted to React elements. Instead of giving a name like div
, you give a function to execute.
First, thanks for great work!
Second, I followed the example and struggled for a few minutes to get this to work in ES6 before I realising that I needed to do import * as d3 from 'd3'
instead of `import d3 from 'd3', as per this thread.
@Olical, do you have any plans to update the docs to include some ES6 usage example, or should I do a simple PR with an ES6 example?
I just use the example from README.md.
var Graph = React.createClass({
propTypes: {
data: React.PropTypes.arrayOf(React.PropTypes.number)
},
render: function () {
var chart = d3.select(ReactFauxDOM.createElement('svg'));
chart
.selectAll('g')
.data(this.props.data)
.enter().append('g')
.classed('bar', true)
.attr('width', function (d, i) {
return d * 10
})
.text((d) => d);
return chart.node().toReact();
}
});
I'm trying to implement a Brush in a chart, but I get an undefined is not a valid argument for 'in' (evaluating 'name in object')
error from the ReactEventListener.
I believe the cause is the brush event from d3.
Could this be implemented in the react-faux-dom?
Hey,
another task on your list when you'll get time: http://bl.ocks.org/mbostock/8fadc5ac9c2a9e7c5ba2
basically:
var zoom = d3.behavior.zoom()
.scaleExtent([1, 8])
.on("zoom", zoomed);
d3fc.layout iterate DOM by childNodes. Thanks.
// creates the structure required by the layout engine
function createNodes(el) {
function getChildNodes() {
var children = [];
for (var i = 0; i < el.childNodes.length; i++) {
var child = el.childNodes[i];
if (child.nodeType === 1) {
if (child.getAttribute('layout-style')) {
children.push(createNodes(child));
}
}
}
return children;
}
return {
style: parseStyle(el.getAttribute('layout-style')),
children: getChildNodes(el),
element: el
};
}
createHook
animation helperIn this blog post I detail a solution on how to make D3 animations work with react-faux-dom, using a small helper function createHook
. You call it with a reference to the component, the faux element you intend to feed to D3, and the state prop name you want the resulting virtual DOM to end up in:
let hook = createHook(this,fauxnode,"chart");
The returned hook is then attached to all D3 animations like this:
rect.transition()
.duration(500)
.attr("y", function(d) { return y(d.y0 + d.y); })
.call(hook);
This will ensure that the following line of code runs while the animation is playing:
component.setState({[statename]:fauxelement.toReact()});
...allowing your render function to simply output this.state.chart
(or whatever statename you chose).
This helper could be added to react-faux-dom
itself, however it kind of itches to add something D3-specific to an otherwise library-agnostic library, intended to work with any DOM-manipulating lib.
Here's my current thinking - the only reason right now to attach stuff to the D3 anims is to be able to stop updating state when the animation is done playing. However, I haven't managed to make the cleanup fool-proof; if you spam transitions, sometimes the relevant listeners won't fire, and my little helper lib will update state indefinitely.
So now I'm thinking we could make a general version that works like this:
You connect
a React component to a faux element using the same signature as createHook
:
ReactFauxDOM.connect(this,faux,"chart");
This will give you an updateFauxDOM
method on the component which you can call whenever you like (and probably the connect
call should schedule an automatic first call much like createHook
works).
// after doing something to the faux node:
this.updateFauxDOM();
And now for the killer feature - the update method takes an optional millisecond argument, which will update the DOM for that amount of time!
rect.transition()
.duration(500)
.delay(function(d, i) { return i * 10; })
.attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
this.updateFauxDOM(1000); // allowing for duration + max delay, with a bit of a margin
So no more brittle cleanup, and no more D3-specific stuff - we simply let the developer tell us how long to animate for. Super easy to implement in a safe way. This could be coupled with an option to animate forever and a .stopAnimating
method, and why not also the isAnimating
method that createHook
added.
Potentially we could make this into a mixin which adds some automagic cleanup in a componentWillUnmount
call.
What do you think, @Olical?
I am trying to implement a tooltip on a line chart that will typically have around 2700 data points. I set it up by starting with the solution from #29. The code below has the state change where instead of a color change, I am updating the opacity from 0 to 1. Once there are lots of data points, it becomes horribly slow. Is there a better way to implement mouseover behavior so that there will not be a huge lag as the mouse moves across the chart?
svg.selectAll('.bar')
.data(data)
.enter().append('rect')
.attr('class', 'annotation-bar')
.attr('x', function(d) { return x(d.Time); })
.attr('y', margin.top)
.attr('height', height - margin.top)
.attr('width', '10px')
.attr('opacity', 0)
.on('mouseover', function(d) {
that.setState({
xLabel: x(d.Time) + margin.left,
yLabel: y(d.HeartRate) + margin.top,
labelHR: d.HeartRate,
labelTime: d.Time,
opacity: '1.0'
});
})
.on('mouseout', function(d) {
that.setState({
xLabel: 0,
yLabel: 0,
labelHR: '',
labelTime: '',
opacity: '0.0'
});
}
);
Hi Oliver,
Most recently I have been in a situation where I need to "convert" the BackboneView to React views. I am wondering if you have any experience of using this project's idea to convert the BackboneView.
Initially, I have to make a "BackboneView | React View|BackboneView | React View" type of component in order to reuse the existing well-maintained in-house BackboneView components, but gradually I found such sandwich format would run into some issue, for example, when we tried to insert the BackboneView component during the componentDidMount
and after the state changed the Backbone component would disappear. Arguably we could re-insert the Backbone View component in a hacky way but not ideal. And rewriting the whole Backbone Component codebase is way more time-consuming on our side.
Thanks in advance,
Hao
Use the example code (animate-d3-with-mixin), put a break point on line 88.
In the debugger execute
rect[0][0].getBoundingClientRect()
returns undefined.
Stepping into Element.prototype.getBoundingClientRect
I see in the first line that the Element doesn't have a component
if (!this.component) {
return undefined
}
not sure what that means(no component) or if I'm using the new api correctly....
I get an error when I run this within a React component:
d3.select(โ.containerโ)
.selectAll(โdiv.dataPointโ)
.data(incomingData)
.enter()
.append(โdivโ)
.attr(โclassโ, โdataPointโ)
.html(function(d) { return d.label })
The error:
bundle.js:7360 Uncaught TypeError: this.querySelectorAll is not a function
I'm using D3 v4.1.1
Currently, toReact
will set faux-dom-0
as the default key
on the react element, when no index
was provided, leading to children with same key
issues - at least when creating multiple elements without index
and passing them as an array. There are situations where it is perfectly fine not to set a key, as React will simply use the array index or similar.
I thus propose to either:
key
at all attribute if index
was not provideduuid
as the key
to prevent collisions (again if index
was not provided)What do you think?
Great library btw. Thanks a lot.
import './style.less';
import React from 'react';
import d3 from 'd3';
import ReactFauxDOM from 'react-faux-dom';
const Component = React.createClass({
render() {
var list = ReactFauxDOM.createElement('ul');
d3.select(list)
.selectAll('li')
.data([1,2,3])
.enter()
.append('li')
.text((d) => d);
return list.toReact();
}
});
export default Component;
So D3 v4.0.0 is out now which includes a number of changes, including ES6 modules so you don't have to pull in everything anymore.
I'm wondering if they changed the use of the DOM significantly though, which would not work with ReactFauxDOM without modification. I haven't tried it yet and maybe it'll Just WorkTM.
Let me know here if you encounter blocking issues. Then if anyone wants to help shim those new methods that'd be amazing. ๐
My code is below. It is possible I'm not doing something correctly. I'm new to everything here, except React.
Note: the error only occurs after the array has 2 or more elements. Also when the array "products" has only one item, no circles show up. Oh and the format of the elements are {id, product}.
ie: [{id: 0, product: 24}], {id:1, product: 36}]
import React from 'react';
import { connect } from 'react-redux';
import ReactFauxDom from 'react-faux-dom';
import d3 from 'd3';
let Visualization = ({products}) => {
let margin = {top: 20, right: 20, bottom: 30, left: 50}
let width = 960 - margin.left - margin.right
let height = 500 - margin.top - margin.bottom
let max = products.reduce((carry, product) => Math.max(product.product, carry), 0);
let x = d3.scale.linear().range([0, width]);
let y = d3.scale.linear().range([0, height]);
let xAxis = d3.svg.axis().scale(x).orient('bottom');
let yAxis = d3.svg.axis().scale(y).orient('left');
let node = ReactFauxDom.createElement('svg');
let svg = d3.select(node)
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ', ' + margin.top+ ')');
x.domain(d3.extent(products, (d) => d.products));
y.domain(d3.extent(products, (d) => d.products));
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis)
svg.append('g')
.attr('class', 'y axis')
.call(yAxis)
console.log('products: ', products);
svg.data(products).enter()
.append('circle') //******HERE********//
.attr('cx', (product) => +product.product)
.attr('cy', (product) => +product.product)
.attr('r', 20);
return node.toReact();
}
Visualization = connect(({products}) => ({products}))(Visualization);
export default Visualization;
Hi,
I'm trying to play with D3 and react so I assume this is the place to be.
I have a typescripted project and I don't know how to make everything work.
I was able to make this example (https://github.com/Olical/lab/blob/gh-pages/js/react-faux-dom-state/main.js) work with some minor tweaks (ie: I extracted the variable ReactFauxDOM.createElement('svg')
and called toReact()
on it instead of svg.node().toReact()
But when I tried another simple animated example (http://bl.ocks.org/jose187/4733747) I ended with a non animated graph really ugly. So I started playing with componentDidMount but I wasn't able to add the svg to the already existing dom.
The source code of my class is here (https://github.com/MichaelBitard/react-blank-project/blob/try_animate/src/components/Topology/Topology.tsx), is there something obvious that I did wrong?
This a lightweight alternative to jsdom for rendering d3 but assumes/requires React.
Perhaps the non-React bits could be factored out into a separate project (faux-dom
? d3-faux-dom
?) and then build react-faux-dom
on that.
I'm not intimately familiar with the code, but it seems like this lib could import d3-faux-dom
and then decorate Element.prototype
with toReact
, add the mixins, etc.
Right now if you need to create a line chart with an an X axis and y Axis, a line and a tooltip.
You end up writing these all into one giant render function all writing these elements to the faux-dom using the d3 library. Every re-render will re-render the whole thing, specially when all you want is move the tooltip around.
Now lets say you want to componentize these.
On the top level I want to create an svg with d3. Then add three sub-components xAxis, yAxis, Line and a Tooltip. I want the tooltip to render independently of the others because usually I want the re-render that on a mouse move.
Proposal
Add an addChild(ReactComponent) support on the faux-dom element.
Have the method be able to specify a custom key so that we can make rendering on one subtree independent of the other.
Thoughts?
import d3 from 'd3'
import React from 'react'
import ReactFauxDOM from 'react-faux-dom'
import * as sankey from './util/sankey'
class Sankey extends React.Component {
static propTypes = {
width: React.PropTypes.number,
height: React.PropTypes.number,
title: React.PropTypes.string
//data: React.PropTypes.array.isRequired,
}
render() {
var units = "Widgets";
var margin = {top: 10, right: 10, bottom: 10, left: 10},
width = 1000 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"), // zero decimal places
format = function (d) {
return formatNumber(d) + " " + units;
},
color = d3.scale.category20();
// append the svg canvas to the page
var svg = ReactFauxDOM.createElement('svg');
var someDiv = d3.select(svg)
// var svg = d3.select("#content").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Set the sankey diagram properties
var sankey = d3.sankey()
.nodeWidth(36)
.nodePadding(40)
.size([width, height]);
var path = sankey.link();
// load the data
d3.json("sankey-formatted.json", function (error, graph) {
var nodeMap = {};
graph.nodes.forEach(function (x) {
nodeMap[x.name] = x;
});
graph.links = graph.links.map(function (x) {
return {
source: nodeMap[x.source],
target: nodeMap[x.target],
value: x.value
};
});
sankey
.nodes(graph.nodes)
.links(graph.links)
.layout(32);
// add in the links
var link = someDiv.append("g").selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function (d) {
return Math.max(1, d.dy);
})
.sort(function (a, b) {
return b.dy - a.dy;
});
// add the link titles
link.append("title")
.text(function (d) {
return d.source.name + " โ " +
d.target.name + "\n" + format(d.value);
});
// add in the nodes
var node = someDiv.append("g").selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
})
.call(d3.behavior.drag()
.origin(function (d) {
return d;
})
.on("dragstart", function () {
this.parentNode.appendChild(this);
})
.on("drag", dragmove));
// add the rectangles for the nodes
node.append("rect")
.attr("height", function (d) {
return d.dy;
})
.attr("width", sankey.nodeWidth())
.style("fill", function (d) {
return d.color = color(d.name.replace(/ .*/, ""));
})
.style("stroke", function (d) {
return d3.rgb(d.color).darker(2);
})
.append("title")
.text(function (d) {
return d.name + "\n" + format(d.value);
});
// add in the title for the nodes
node.append("text")
.attr("x", -6)
.attr("y", function (d) {
return d.dy / 2;
})
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function (d) {
return d.name;
})
.filter(function (d) {
return d.x < width / 2;
})
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
// the function for moving the nodes
function dragmove(d) {
d3.select(this).attr("transform",
"translate(" + (
d.x = Math.max(0, Math.min(width - d.dx, d3.event.x))
)
+ "," + (
d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))
) + ")");
sankey.relayout();
link.attr("d", path);
}
});
return (
<div>{someDiv.node().toReact()}</div>
);
}
}
export default Sankey
There is no output in the browser. Any ideas as in what I am missing in here?
Hi,
I am trying to add slider using react-faux-dom.
As I am absolute beginner with react, I am not sure what to do in handleDrag() function.
Any suggestions on updating values after a slider is dragged?
Thank you
import d3 from 'd3'
import React from 'react'
import ReactFauxDOM from 'react-faux-dom'
class Slider extends React.Component {
componentDidMount() {
var drag = d3.behavior.drag()
.on("drag", this.handleDrag)
var dom = React.findDOMNode(this);
d3.select(dom).call(drag)
}
handleDrag() {
var posY = d3.mouse(React.findDOMNode(this)),
newValue = Math.max(0, Math.min(1,posY/400))
//this.onChange(newValue);
//not sure what to do here
//need advice
}
render() {
const {width, height, value} = this.props
const el = d3.select(ReactFauxDOM.createElement('svg'))
.attr('width',width)
.attr('height',height)
el.append("rect")
.attr('width',width*value)
.attr('height',height)
.attr('class',"Slider-fill")
el.append("rect")
.attr('width',width)
.attr('height',height)
.attr('class',"Slider-border")
el.append("text")
.attr('x',width/2)
.attr('y',height/2)
.text(value)
return el.node().toReact();
}
}
export default Slider
But it also dropped IE 6-8 support.
Hey !
I had just a 'under the hood' question, imagine I have a state { width: 100 }
that I apply to my d3 svg container to define its width. When the state width
is updated, is the full svg redrawed or is just the attribute width
updated ?
Hi,
when calling 'toReact()' it mutates the faux element (at least it seems to delete some functions on the style property) and therefore the faux element can not be reused between renderings and has to be recreated on every render.
Does this not stop us from using the enter-update-exit pattern in d3 since we are always starting with a new faux element?
cheers,
mg
This involves the following changes which should help with things like #4.
addEventListener
multiple times should allow you to add multiple listeners.Right now I have a dumb approach which removes the previous listener and replaces it because I'm just setting props.onClick
, for example. This needs to basically delegate to some event system that allows you to add or remove multiple listeners. An abstraction on top of Reacts event system to make it behave like the DOM.
This should allow things like d3.mouse
to work, right now it completely fails, you have to hit d3.event
and work it out yourself.
Hey,
Great work on the library - it worked first time for me, hardly any code required to get my D3 chart rendering in react!
The only issue I have is that animation doesn't work. I know you mentioned on your blog that this is something you might work on in the future - any update on this?
Cheers,
Prash
I'm currently writing a a little lib for my job using D3 and React. So I decided to utilize your library to try to get it done efficiently between React and D3 Also, thanks for this library it is great!
Im trying to write a reusable BarGraph
component. While I was working on this step by step I saw the bars working throughout. However as soon as I started adding axis
to the component it no longer rendered the bars that model data. It's odd because I inspect the console and the rect
are rendered to the DOM however there is nothing displayed aside from the axis
data. I've been trying to get this working for 2 days now and am at a loss.
BarGraphComponent
class BarGraph extends React.Component {
render () {
const {data, width, height} = this.props;
const innerW = width - 70,
innerH = height - 50;
var x = d3.scale.ordinal()
.domain(data.map((d) => d.name))
.rangeRoundBands([0, innerW], .1);
var y = d3.scale.linear()
.domain([0, d3.max(data, (d) => d.value)])
.range([innerH, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
var yAxis = d3.svg.axis()
.scale(y)
.orient('left');
var chart = d3.select(ReactFauxDOM.createElement('svg'))
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', `translate(${40}, ${20})`);
chart.append('g')
.attr('class', 'axis x')
.attr('transform', `translate(0, ${innerH})`)
.call(xAxis)
chart.append('g')
.attr('class', 'axis y')
.call(yAxis);
chart.selectAll('.bar')
.data(data)
.enter().append('rect')
.attr('class', 'bar')
.attr('x', (d) => x(d.name))
.attr('y', (d) => y(d.value))
.attr('height', (d) => innerH - y(d.value))
.attr('width', x.rangeBand());
return chart.node().toReact();
}
}
BarGraph.proptypes = {
width: React.PropTypes.number,
height: React.PropTypes.number,
data: React.PropTypes.array,
}
The code above only renders this:
Elements rendered in the console
It is based off of this tutorial from the creator of D3:
https://bost.ocks.org/mike/bar/3/
If the problem is obvious or you can tell what I'm doing wrong please let me know I've been having a hard time debugging this mostly because I'm a bit newer to d3.
I need to document the full extent of the API with examples. More importantly, I need to document adding features that are missing.
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.