React component with Dagre-D3 not drawing correctly - d3.js

I converted this Dagre-D3 demo to a React component. The code is below.
import React from 'react'
import d3 from 'd3'
import dagreD3 from 'dagre-d3'
export default class D3Chart extends React.Component {
constructor () {
super();
}
componentDidMount() {
// Create the input graph
var g = new dagreD3.graphlib.Graph()
.setGraph({})
.setDefaultEdgeLabel(function() { return {}; });
// Here we"re setting nodeclass, which is used by our custom drawNodes function
// below.
g.setNode(0, { label: "TOP", class: "type-TOP" });
g.setNode(1, { label: "S", class: "type-S" });
g.setNode(2, { label: "NP", class: "type-NP" });
g.setNode(3, { label: "DT", class: "type-DT" });
g.setNode(4, { label: "This", class: "type-TK" });
g.setNode(5, { label: "VP", class: "type-VP" });
g.setNode(6, { label: "VBZ", class: "type-VBZ" });
g.setNode(7, { label: "is", class: "type-TK" });
g.setNode(8, { label: "NP", class: "type-NP" });
g.setNode(9, { label: "DT", class: "type-DT" });
g.setNode(10, { label: "an", class: "type-TK" });
g.setNode(11, { label: "NN", class: "type-NN" });
g.setNode(12, { label: "example", class: "type-TK" });
g.setNode(13, { label: ".", class: "type-." });
g.setNode(14, { label: "sentence", class: "type-TK" });
g.nodes().forEach(function(v) {
var node = g.node(v);
// Round the corners of the nodes
node.rx = node.ry = 5;
});
// Set up edges, no special attributes.
g.setEdge(3, 4);
g.setEdge(2, 3);
g.setEdge(1, 2);
g.setEdge(6, 7);
g.setEdge(5, 6);
g.setEdge(9, 10);
g.setEdge(8, 9);
g.setEdge(11,12);
g.setEdge(8, 11);
g.setEdge(5, 8);
g.setEdge(1, 5);
g.setEdge(13,14);
g.setEdge(1, 13);
g.setEdge(0, 1)
// Create the renderer
var render = new dagreD3.render();
// Set up an SVG group so that we can translate the final graph.
var svg = d3.select(React.findDOMNode(this.refs.nodeTree));
var svgGroup = d3.select(React.findDOMNode(this.refs.nodeTreeGroup));
// Run the renderer. This is what draws the final graph.
render(d3.select(React.findDOMNode(this.refs.nodeTreeGroup)), g);
// Center the graph
var xCenterOffset = (svg.attr("width") - g.graph().width) / 2;
svgGroup.attr("transform", "translate(" + xCenterOffset + ", 20)");
svg.attr("height", g.graph().height + 40);
}
render() {
return (<svg id="nodeTree" ref="nodeTree" width="960" height="600"><g ref="nodeTreeGroup"/></svg>
)
};
}
The problem is that the rendering of the nodes are mis-aligned and their sizes too.
This is how it looks like. How it should like is here.
UPDATE:
This is how the first node looks like:
What now:
<g class="node type-TOP" transform="translate(100,0)" style="opacity: 1;"><rect rx="5" ry="5" x="-10" y="-10" width="20" height="20"></rect><g class="label" transform="translate(0,0)"><g transform="translate(0,0)"><text><tspan xml:space="preserve" dy="1em" x="1">TOP</tspan></text></g></g></g>
What should be:
<g class="node type-TOP" transform="translate(211.25,18)" style="opacity: 1;"><rect rx="5" ry="5" x="-24.5" y="-18" width="49" height="36"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-14.5,-8)"><text><tspan xml:space="preserve" dy="1em" x="1">TOP</tspan></text></g></g></g>
The width and height are not calculated correctly. The width should be 49 but it is only 20.

Try to set the height and width inside the componentDidMount just after you caught the svg with findDOMNode.
Or try to put the height and width this way.
style={{height:'900', width:'300'}}
Let me know if it works

I've figured out a workaround for this. I am having the exact same issue as you, and through pure trial and error I discovered that if you append the graph to the body of the page, it works fine. The text and nodes line up and the node sizes change appropriately to the text. This isn't ideal, but is a workaround..
(I don't know react syntax, so bear with me)
Instead of having a svg node pre created like this (don't do this):
var svg = d3.select(React.findDOMNode(this.refs.nodeTree));
Build one on the fly and append it to the body element of your whole HTML doc (do this):
var svg = d3.select(React.findDOMNode('body'))
.append('svg')
.attr('width', 500)
.attr('height', 500);
I'm not sure why this is, possibly the reason you explained in the comments of François Richard's answer.

Related

react native navigation custom animated transition

I'm using react native v0.49 and I'm trying to implement custom transition when navigate to other page.
what I'm trying to do is to make transition only for one scene from scene 2 to scene3. but not for all the app.
this example I found it's for all web so I want to make just for one screen and for all the app because if I do that way it will effect for all the app and it's not what I'm looking for. any idea?
class SceneOne extends Component {
render() {
return (
<View>
<Text>{'Scene One'}</Text>
</View>
)
}
}
class SceneTwo extends Component {
render() {
return (
<View>
<Text>{'Scene Two'}</Text>
</View>
)
}
}
let AppScenes = {
SceneOne: {
screen: SceneOne
},
SceneTwo: {
screen: SceneTwo
},
SceneThree: {
screen: SceneTwo
},
}
let MyTransition = (index, position) => {
const inputRange = [index - 1, index, index + 1];
const opacity = position.interpolate({
inputRange,
outputRange: [.8, 1, 1],
});
const scaleY = position.interpolate({
inputRange,
outputRange: ([0.8, 1, 1]),
});
return {
opacity,
transform: [
{scaleY}
]
};
};
let TransitionConfiguration = () => {
return {
// Define scene interpolation, eq. custom transition
screenInterpolator: (sceneProps) => {
const {position, scene} = sceneProps;
const {index} = scene;
return MyTransition(index, position);
}
}
};
class App extends Component {
return (
<View>
<AppNavigator />
</View>
)
}
Here's an example of how we do it, you can add your own transitions to make it your own. Our goal was simply to expose the baked-in transition configurations to have more control over the animations. Our transition configuration: https://gist.github.com/jasongaare/db0c928673aec0fba7b4c8d1c456efb6
Then, in your StackNavigator, add that config like so:
StackNavigator(
{
LoginScreen: { screen: LoginScreen },
HomeScreen: { screen: HomeScreen },
},
{
stateName: 'MainStack',
initialRouteName: 'HomeScreen',
initialRouteParams: { transition: 'fade' },
transitionConfig: TransitionConfig,
}
);
Finally, when you navigate, just add your params when you navigate:
this.props.navigation.navigate('HomeScreen', { transition: 'vertical' })

d3 zoom behavior in React Native

In React Native I draw my charts with d3 and ART libraries.
export default class DevTest extends React.Component {
static defaultProps = {
width: 300,
height: 150,
}
mainScaleX = d3Scale.scaleTime()
.domain(d3Array.extent(chartDataTo, data => data.date))
.range([0, this.props.width])
mainScaleY = d3Scale.scaleLinear()
.domain(d3Array.extent(chartDataTo, data => data.value))
.range([this.props.height, 0])
rawChartValue = d3Shape.line()
.x(data => this.mainScaleX(data.date))
.y(data => this.mainScaleY(data.value))
render(){
return (
<View>
<Surface width = {this.props.width} height = {this.props.height}>
<Group x = {0} y = {0}>
<Shape
d = {this.rawChartValue(chartData)}
stroke = "#000"
strokeWidth = {1}
/>
</Group>
</Surface>
</View>
)
}
}
Is there any way to implement d3 svg-like zoom behavior, but without DOM mutation. For example, using some d3 pure functions with just x and y values as input to rescale mainScaleX and mainScaleY?
You can use PanResponder from react-native and zoomIdentity from d3-zoom for that purpose.
Code example looks sometihng like this
componentWillMount() {
this._panResponder = PanResponder.create({
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onPanResponderMove: (event, { dx, dy }) => {
const zoomIdentity = d3Zoom.zoomIdentity.translate(dx, dy)
this._onDrag(zoomIdentity)
},
});
}
_onDrag(zoomIdentity){
this.setState({
rescaledX: zoomIdentity.rescaleX(this.state.mainScaleX),
rescaledY: zoomIdentity.rescaleY(this.state.mainScaleY)
})
}
And you pass your rescaled{X, Y} to a chart component
render(){
return (
<View {...this._panResponder.panHandlers}>
<Component
{...this.props}
scaleX = {this.state.rescaledX}
scaleY = {this.state.rescaledY}
/>
</View>
)
}

Get all the data from a clicked node in C3JS

How do I get all the data from a node when clicked? Right now I get only the x and value.
How do I get all the values from the JSON data object used for plotting the graph?
var chart_scatterplot = c3.generate({tooltip: {
contents: function(d, defaultTitleFormat, defaultValueFormat, color) {
var company = jsonfile[d[0].index].company;
var mailCount = jsonfile[d[0].index].mailCount;
var lastInteractedInDays = jsonfile[d[0].index].lastInteractedInDays;
var companyData = "<table class='data-c3-table'><tr><td>" + company + "</td></tr><tr><td>" + mailCount + "</td></tr><tr><td>" + lastInteractedInDays + "</td></tr></table>"
return companyData;
//return (company+mailCount+lastInteractedInDays) // formatted html as youmailCount want
}},
point: {r: 7},
data: {
json: jsonfile,
x: 'mailCount',
keys: {
value: ['mailCount', 'lastInteractedInDays'],
},
color: function(color, d) {
if (d.value > average) {
return "#F86A52"
} else {
return "#49B5A6"
};
},
type: 'scatter',
onclick: function(d) {
abc(d);
}
},axis: {
x: {
label: 'Interactions',
tick: {
fit: false
}
},
y: {
label: 'Days'
}},legend: {
show: false}});
var abc = function(v) {console.log("hello" + JSON.stringify(v));}
Attached is a fiddle - https://jsfiddle.net/npmarkunda/eqfyeeh1/
This will give you the full data associated with the node on click.
onclick: function(d) {
console.log(jsonfile[d.index]);//will console. the clicked node's data.
}
Working code here

React 0.14-RC-1 -- OnMouseEnter & OnMouseLeave -- Change state / style

I am trying to change the opacity of an element, per mouseEnter and mouseLeave events:
import React, { Component, PropTypes } from 'react';
import d3 from 'd3';
import Bar from './Bar';
import lodash from 'lodash';
export default class DataSeries extends Component {
static propTypes = {
availableHeight: PropTypes.number,
data: PropTypes.array,
height: PropTypes.number,
offset: PropTypes.number,
width: PropTypes.number
}
constructor() {
super();
this.state = ({
opacity: 1
});
}
onMouseEnterHandler() {
this.setState({ opacity: 0.5 });
}
onMouseLeaveHandler() {
this.setState({ opacity: 1 });
}
render() {
const props = this.props;
const yScale = d3.scale.linear()
.domain([0, d3.max(this.props.data)])
.range([0, this.props.height]);
const xScale = d3.scale.ordinal()
.domain(d3.range(this.props.data.length))
.rangeRoundBands([0, this.props.width], 0.05);
const colors = d3.scale.linear()
.domain([0, this.props.data.length])
.range(['#FFB832', '#C61C6F']);
const bars = lodash.map(this.props.data, function(point, index) {
return (
<Bar height={ yScale(point) } width={ xScale.rangeBand() }
offset={ xScale(index) } availableHeight={ props.height }
color={ colors(point) } key={ index }
onMouseEnter={ () => this.onMouseEnterHandler() }
onMouseLeave={ () => this.onMouseLeaveHandler() }
style = { { opacity: this.state.opacity } } <-- error points here
/>
);
});
return (
<g>{ bars }</g>
);
}
}
DataSeries.defaultPropTypes = {
title: '',
data: []
};
I am receiving the error:
TypeError: Cannot read property 'state' of undefined(…)
I see one issue is 'this' needs to be passed in. The other change is probably optional.
let vm = this;
let bars = lodash.map(this.props.data, function(point, index, vm) {
let opacity = { opacity: vm.state.opacity };
return (
<Bar height={ yScale(point) } width={ xScale.rangeBand() }
offset={ xScale(index) } availableHeight={ props.height }
color={ colors(point) } key={ index }
onMouseEnter={ vm.onMouseEnterHandler }
onMouseLeave={ vm.onMouseLeaveHandler }
style = { opacity }
/>
);
});

Open curtain animation on a famo.us Scrollview

I'm trying to create an open curtain animation on a Scrollview, so when an item in a Scrollview is clicked, it and item to its left move left, and all items after it move to the right. when the item is clicked again, the curtain closes. Thinking of doing it by moving items out from the Scrollview to 2 SequentialLayouts and once the curtain closes, move them back into the Scollview. Can this be done? Can you move nodes / views around in the render tree from one node to another?
Any other design approaches I should consider?
Here is my version of the curtains you described. It was hard to know exactly what you wanted, but I took a stab at it.
define('main', function(require, exports, module) {
var Engine = require("famous/core/Engine");
var Surface = require("famous/core/Surface");
var RenderNode = require("famous/core/RenderNode");
var Modifier = require("famous/core/Modifier");
var Utility = require("famous/utilities/Utility");
var Scrollview = require("famous/views/Scrollview");
var Transitionable = require("famous/transitions/Transitionable");
var SnapTransition = require("famous/transitions/SnapTransition");
Transitionable.registerMethod('snap', SnapTransition);
var snap = {
method: 'snap',
period: 600,
dampingRatio: 0.6
};
var mainContext = Engine.createContext();
var scrollview = new Scrollview({
direction: Utility.Direction.X
});
var views = [];
scrollview.sequenceFrom(views);
function _resize(index, views, event) {
console.log(index, event);
var itsMe = (index === event.index);
if (itsMe) {
this.trans.halt();
if (this.open)
this.trans.set(100, snap);
else
this.trans.set(400, snap);
this.open = !this.open;
scrollview.goToPage(index);
} else {
if (event.isOpen) {
this.trans.halt();
this.trans.set(100, snap);
} else {
this.trans.halt();
this.trans.set(20, snap);
}
this.open = false;
}
}
function _resizeChosen(index, views, event) {
scrollview._eventOutput.emit('itemChosen', {
index: index,
isOpen: views[index].surface.open
});
}
function _surfaceSize() {
return [this.trans.get(), undefined];
}
for (var i = 0; i < 20; i++) {
var node = new RenderNode();
node.surface = new Surface({
content: (i + 1),
size: [undefined, undefined],
properties: {
backgroundColor: "hsl(" + (i * 360 / 20) + ", 90%, 50%)",
lineHeight: "50px",
textAlign: "center"
}
});
node.surface._index = i;
node.surface.open = false;
node.surface.state = new Modifier();
node.surface.trans = new Transitionable(50);
node.surface.state.sizeFrom(_surfaceSize.bind(node.surface));
node.add(node.surface.state).add(node.surface);
node.surface.pipe(scrollview);
node.surface._eventOutput.subscribe(scrollview._eventOutput);
node.surface.on('click', _resizeChosen.bind(node.surface, i, views));
node.surface.on('itemChosen', _resize.bind(node.surface, i, views));
views.push(node);
}
mainContext.add(scrollview);
});
require(['main']);
<script src="http://requirejs.org/docs/release/2.1.16/minified/require.js"></script>
<script src="http://code.famo.us/lib/requestAnimationFrame.js"></script>
<script src="http://code.famo.us/lib/classList.js"></script>
<script src="http://code.famo.us/lib/functionPrototypeBind.js"></script>
<link rel="stylesheet" type="text/css" href="http://code.famo.us/famous/0.3.5/famous.css" />
<script src="http://code.famo.us/famous/0.3.5/famous.min.js"></script>

Resources