I am new to all D3/react coding. I am converting D3 v3 example to D3 v4 (React-DC). I am able to convert for Pie chart and Row chart. But I am stuck at Bar Chart and Area Chart. It seems to be a minor issue, as I can see the rendered charts but with following issues (sorry in advance for a lengthy content):
Bar chart: The bars are 1 pixel wide. I checked on DC js FAQ page in github, they say I have to set xUnits. But, I have xUnits defined, but still it won't work.
Area chart (Stacked): Only shows one? I don't see the rest of the stacks.
I converted this working d3.js v3 example
<BarChart
dimension={byYear}
group={byYearGroup}
x={d3.time.scale()}
renderHorizontalGridLines={true}
elasticX={true}
xUnits={d3.time.years}
ordinalColors={colors}
/>
<AreaChart
dimension={byYear}
group={rockByYearGroup}
x={d3.time.scale()}
renderHorizontalGridLines={true}
elasticX={true}
xUnits={d3.time.years}
renderArea={true}
stack={[popByYearGroup, funkSoulByYearGroup, electroByYearGroup, hipHopByYearGroup]}
xyTipsOn={true}
brushOn={false}
ordinalColors={colors}
/>
Output:
to this d3.js v4 example
<BarChart
dimension={byYear}
group={byYearGroup}
x={d3.scaleTime()}
renderHorizontalGridLines={true}
elasticX={true}
xUnits={d3.timeYears}
ordinalColors={colors}
/>
<AreaChart
dimension={byYear}
group={rockByYearGroup}
x={d3.scaleTime()}
renderHorizontalGridLines={true}
elasticX={true}
xUnits={d3.timeYears}
renderArea={true}
stack={[popByYearGroup, funkSoulByYearGroup, electroByYearGroup, hipHopByYearGroup]}
xyTipsOn={true}
brushOn={false}
ordinalColors={colors}
/>
Output:
These are my imports on the page:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import crossfilter from 'crossfilter'
import {BarChart, AreaChart, PieChart, RowChart, ScatterPlot, SeriesChart} from './reactdc/react-dc'
import './reactdc/react-dc.css'
import albums from './data/albums'
import * as d3 from 'd3';
import dc from 'dc'
Also, I have converted the coordinate-grid-mixin.js to v4 standards:
import React from 'react'
import PropTypes from 'prop-types'
import {compose, withProps} from '../utils'
import colorMixin from './color-mixin'
import marginMixin from './margin-mixin'
import baseMixin from './base-mixin'
const coordinateGridMixin = withProps({
brushOn: PropTypes.bool,
clipPadding: PropTypes.number,
elasticX: PropTypes.bool,
elasticY: PropTypes.bool,
focus: PropTypes.arrayOf(PropTypes.number),
mouseZoomable: PropTypes.bool,
//rangeChart??????
renderHorizontalGridLines: PropTypes.bool,
renderVerticalGridLines: PropTypes.bool,
round: PropTypes.func,
useRightYAxis: PropTypes.bool,
x: PropTypes.any.isRequired, // TO DO : PropTypes.any d3 quantitive scale or ordinal scale
xAxis: {
propTypes: PropTypes.shape({
orient: PropTypes.string,
ticks: PropTypes.array,
tickValues: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string, Date])),
tickSize: PropTypes.arrayOf(PropTypes.number),
innerTickSize: PropTypes.number,
outerTickSize: PropTypes.number,
tickPadding: PropTypes.number,
tickFormat: PropTypes.func
}),
setter(method, val){
Object.entries(val).forEach(([key, value]) => method()[key](value))
}
},
xAxisLabel: {
propTypes: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({
labelText: PropTypes.string.isRequired,
padding: PropTypes.number.isRequired
})]),
setter(method, val){
if (val.labelText && val.padding){
method(val.labelText, val.padding)
}else{
method(val)
}
}
},
xAxisPadding: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
xAxisUnit: PropTypes.string,
y: PropTypes.any, // TO DO : d3 scale
xUnits: PropTypes.func,
yAxis: {
propTypes: PropTypes.shape({
orient: PropTypes.string,
ticks: PropTypes.array,
tickValues: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string, Date])),
tickSize: PropTypes.arrayOf(PropTypes.number),
innerTickSize: PropTypes.number,
outerTickSize: PropTypes.number,
tickPadding: PropTypes.number,
tickFormat: PropTypes.func
}),
setter(method, val){
Object.entries(val).forEach(([key, value]) => method()[key](value))
}
},
yAxisLabel: {
propTypes: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({
labelText: PropTypes.string.isRequired,
padding: PropTypes.number.isRequired
})]),
setter(method, val){
if (val.labelText && val.padding){
method(val.labelText, val.padding)
}else{
method(val)
}
}
},
yAxisPadding: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
zoomOutRestrict: PropTypes.bool,
zoomScale: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.number), PropTypes.arrayOf(PropTypes.instanceOf(Date))])
})
export default compose(coordinateGridMixin, colorMixin, marginMixin, baseMixin)
I am not sure what am I missing. There are not many v4 example on web either.
My library versions if it would help:
{
"dependencies": {
"crossfilter": "^1.3.12",
"d3": "^4.13.0",
"d3-scale": "^2.0.0",
"d3-shape": "^1.2.0",
"dc": "^2.1.9",
"prop-types": "^15.6.0",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-scripts": "1.1.0",
"recompose": "^0.26.0"
}
Update #1
I did some more digging and found that the following line of code in dc.js file return nothing.
var units = _chart.xUnits()(_chart.x().domain()[0], _chart.x().domain()[1], _chart.x().domain());
When I step into this line, it calls a range function (a modified version from newer d3.js (v.4.13.0) -> interval.js file) which is moved to interval.js file and expects step value as the last parameter.
interval.range = function(start, stop, step) {
var range = [], previous;
start = interval.ceil(start);
step = step == null ? 1 : Math.floor(step);
if (!(start < stop) || !(step > 0)) return range; // also handles Invalid Date
do range.push(previous = new Date(+start)), offseti(start, step), floori(start);
while (previous < start && start < stop);
return range;
};
as compared to this older version which works because the last parameter accepts an array
function range(t0, t1, dt) {
var time = ceil(t0), times = [];
if (dt > 1) {
while (time < t1) {
if (!(number(time) % dt)) times.push(new Date(+time));
step(time, 1);
}
} else {
while (time < t1) times.push(new Date(+time)), step(time, 1);
}
return times;
}
Related
I am using Yjs with codemirrow 6 in my project. I followed codemirror 6 examples and Yjs codemirror.next example (https://github.com/yjs/y-codemirror.next) in github.
And when I ran it, I got this error.
Here is the source.
Please help.
Any idea is appreciated. Thanks.
================
Update 1
Here is my demo.ts
import * as Y from 'yjs'
// #ts-ignore
import { yCollab, yUndoManagerKeymap } from 'y-codemirror.next'
import { WebrtcProvider } from 'y-webrtc'
import {EditorState, EditorView, basicSetup} from "#codemirror/next/basic-setup"
import {html} from "#codemirror/next/lang-html"
//import {esLint} from "#codemirror/next/lang-javascript"
// #ts-ignore
//import Linter from "eslint4b-prebuilt"
//import {linter} from "#codemirror/next/lint"
//import {StreamLanguage} from "#codemirror/next/stream-parser"
//import {javascript} from "#codemirror/next/legacy-modes/mode/javascript"
import * as random from 'lib0/random'
export const usercolors = [
{ color: '#30bced', light: '#30bced33' },
{ color: '#6eeb83', light: '#6eeb8333' },
{ color: '#ffbc42', light: '#ffbc4233' },
{ color: '#ecd444', light: '#ecd44433' },
{ color: '#ee6352', light: '#ee635233' },
{ color: '#9ac2c9', light: '#9ac2c933' },
{ color: '#8acb88', light: '#8acb8833' },
{ color: '#1be7ff', light: '#1be7ff33' }
]
// select a random color for this user
export const userColor = usercolors[random.uint32() % usercolors.length]
const ydoc = new Y.Doc()
const provider = new WebrtcProvider('codemirror.doc', ydoc)
const ytext = ydoc.getText('codemirror')
const undoManager = new Y.UndoManager(ytext)
provider.awareness.setLocalStateField('user', {
name: 'Anonymous ' + Math.floor(Math.random() * 100),
color: userColor.color,
colorLight: userColor.light
})
let state = EditorState.create({doc: ytext.toString(), extensions: [
basicSetup,
html(),
yCollab(ytext, provider.awareness, { undoManager })
// linter(esLint(new Linter)),
// StreamLanguage.define(javascript),
]})
;(window as any).view = new EditorView({state, parent: document.querySelector("#editor")!})
I AutocompleteInput wrapped ReferenceInput. In my case One Project has many Accounts. On edit account page I set project from available and save.
<ReferenceInput source="project_id" reference="projects" allowEmpty filterToQuery={searchText => ({ query_content: searchText })}>
<AutocompleteInput optionText="title" />
</ReferenceInput>
And now I need to set null for value project_id. It can even button which I could place near AutocompleteInput, but I don't know how set value straight to redux. Preferably I would like to avoid special http-request to API to reset this field.
Thanks!
i just took AutocompleteInput and append to it button to clear field. and call NullableAutocompleteInput. then also as earlier paste to ReferenceInput
<ReferenceInput source="field_id" reference="resources" allowEmpty filterToQuery={searchText => ({ query_title: searchText })}>
<NullableAutocompleteInput optionText={(choice) => optionRenderer(choice, 'Resource', 'title')} />
</ReferenceInput>
and whole code component
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import AutoComplete from 'material-ui/AutoComplete';
import BackspaceIcon from 'material-ui/svg-icons/content/backspace';
import IconButton from 'material-ui/IconButton';
import {FieldTitle} from 'admin-on-rest';
import {translate} from 'admin-on-rest';
export class NullableAutocompleteInput extends Component {
handleNewRequest = (chosenRequest, index) => {
if (index !== -1) {
const { choices, input, optionValue } = this.props;
input.onChange(choices[index][optionValue]);
}
}
getSuggestion(choice) {
const { optionText, translate, translateChoice } = this.props;
const choiceName = typeof optionText === 'function' ? optionText(choice) : choice[optionText];
return translateChoice ? translate(choiceName, { _: choiceName }) : choiceName;
}
clearData() {
this.props.input.onChange(null);
}
render() {
const {
choices,
elStyle,
filter,
input,
isRequired,
label,
meta: { touched, error },
options,
optionValue,
resource,
setFilter,
source,
} = this.props;
const selectedSource = choices.find(choice => choice[optionValue] === input.value);
const dataSource = choices.map(choice => ({
value: choice[optionValue],
text: this.getSuggestion(choice),
}));
return (
<div>
<AutoComplete
searchText={selectedSource && this.getSuggestion(selectedSource)}
dataSource={dataSource}
floatingLabelText={<FieldTitle label={label} source={source} resource={resource} isRequired={isRequired} />}
filter={filter}
onNewRequest={this.handleNewRequest}
onUpdateInput={setFilter}
openOnFocus
style={elStyle}
errorText={touched && error}
{...options}
/>
<IconButton onTouchTap={this.clearData.bind(this)} tooltip="Clear Data" tooltipPosition="top-right">
<BackspaceIcon color='grey' hoverColor='black'/>
</IconButton>
</div>
);
}
}
NullableAutocompleteInput.propTypes = {
addField: PropTypes.bool.isRequired,
choices: PropTypes.arrayOf(PropTypes.object),
elStyle: PropTypes.object,
filter: PropTypes.func.isRequired,
input: PropTypes.object,
isRequired: PropTypes.bool,
label: PropTypes.string,
meta: PropTypes.object,
options: PropTypes.object,
optionElement: PropTypes.element,
optionText: PropTypes.oneOfType([
PropTypes.string,
PropTypes.func,
]).isRequired,
optionValue: PropTypes.string.isRequired,
resource: PropTypes.string,
setFilter: PropTypes.func,
source: PropTypes.string,
translate: PropTypes.func.isRequired,
translateChoice: PropTypes.bool.isRequired,
};
NullableAutocompleteInput.defaultProps = {
addField: true,
choices: [],
filter: AutoComplete.fuzzyFilter,
options: {},
optionText: 'name',
optionValue: 'id',
translateChoice: true,
};
export default translate(NullableAutocompleteInput);
I'm trying to recreate RxMarbles for RxJS 5, but I'm having feedback problems when I change the collection's data (specifically the length of the data source).
I added console.logs for debugging
Note for those who are familiar with RxMarbles, I renamed "Diagram" to "Timeline".
import { svg } from '#cycle/dom';
import isolate from '#cycle/isolate';
import { Observable } from 'rxjs';
import { apply, flip, map, max, merge, path, prop, sortBy, zip } from 'ramda';
import { Collection } from '../collection';
import { Marble } from './marble';
import { EndMarker } from './end-marker';
function sortMarbleDoms$(marbles$) {
const doms$ = Collection.pluck(marbles$, prop('DOM'));
const dataList$ = Collection.pluck(marbles$, prop('data'));
return Observable.combineLatest(doms$, dataList$, zip)
.map(sortBy(path([1, 'time'])))
.map(map(prop(0)));
}
function OriginalTimeline({ DOM, marbles: marblesState$, end: end$ }) {
const marblesProps$ = end$.map(({ time }) => ({
minTime: 0,
maxTime: time,
}));
const endMarkerProps$ = marblesState$.map(marbles => ({
minTime: marbles.map(prop('time')).reduce(max, 0),
maxTime: 100,
}));
const marblesSources = { DOM, props: marblesProps$ };
const endMarkerSources = {
DOM,
props: endMarkerProps$,
time: end$.pluck('time'),
};
const marbles$ = Collection.gather(
Marble, marblesSources, marblesState$
.do(a=>console.log('marblesState', a)), '_itemId');
const marbleDOMs$ = sortMarbleDoms$(marbles$);
const endMarker = EndMarker(endMarkerSources);
const vtree$ = Observable.combineLatest(marbleDOMs$, endMarker.DOM)
.map(([marbleDOMs, endMarkerDOM]) =>
svg({
attrs: { viewBox: '0 0 100 10' },
style: { width: 500, height: 50, overflow: 'visible' },
}, [
svg.line({
attrs: { x1: 0, x2: 100, y1: 5, y2: 5 },
style: { stroke: 'black', strokeWidth: 0.4 },
}),
endMarkerDOM,
...marbleDOMs,
])
);
const marbleData$ = Collection.pluck(marbles$, prop('data'))
.withLatestFrom(marblesState$, zip)
.map(map(apply(flip(merge))))
const data$ = Observable.combineLatest(marbleData$, endMarker.time)
.map(([marbles, endMarkerTime]) => ({
marbles,
end: { time: endMarkerTime },
}))
.debounceTime(1);
return { DOM: vtree$, data: data$.do(a=>console.log('tdata', a)) };
}
export function Timeline(sources) {
return isolate(OriginalTimeline)(sources);
}
The basic structure of the app is that all necessary data is fed into a global sink to a dummy driver that just takes the data and re-emits it as is (so in theory, all outputs should be new inputs).
Because of this, the problem might be in other parts of my code so I'm happy to post a codepen/plunkr of the code if it helps. This is indeed working sometimes, but not all the time.
Here's the console outputs (abridged)
store Object {route: "merge", inputs: undefined}
timeline.js:39 marblesState [Object, Object, Object, Object]
timeline.js:69 tdata Object {marbles: Array[3], end: Object}
sandbox.js:48 data [Object, Object]
app.js:26 store Object {route: "merge", inputs: Array[2]}
Notice the marblesState has 4 objects, but the tdata returns marbles with an array of 3 objects. For some reason, the Collection is only returning 3 items.
Any help is appreciated. Thanks!
I have no idea why this makes sense but moving up the debounceTime(1) made it work
const marbleData$ = Collection.pluck(marbles$, prop('data'))
.debounceTime(1)
.withLatestFrom(marblesState$, zip)
.map(map(apply(flip(merge))))
const data$ = Observable.combineLatest(marbleData$, endMarker.time)
.map(([marbles, endMarkerTime]) => ({
marbles,
end: { time: endMarkerTime },
}));
The Collection.pluck was sending once for each piece of new and old data.
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 }
/>
);
});
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.