React component called twice (AJAX call async) - javascript

What I want to do :
I would like to create a pie chart with the javascript library d3.js. I get data from my server with an AJAX call, and then create my piechart with it.
What I've done :
For this I created 2 react component : one which implements the ajax call and pass his state to the other component which create the pie chart with the data in props.
First component :
import React from 'react';
import Pie from './pie.jsx';
class RingChart extends React.Component {
loadPieChart() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: (data) => {
let d3data = Object.keys(data)
.filter(key => key!= 'pourcentage')
.map(key => { return {name: key, value: data[key]} });
this.setState({dataPie: d3data, percent: data['pourcentage']});
},
error: (xhr, status, err) => {
console.error(this.props.url, status, err.toString());
}
});
}
constructor(props) {
super(props);
this.state = {dataPie: [], percent: 0};
}
componentDidMount() {
this.loadPieChart();
setInterval(this.loadCommentsFromServer, this.props.pollInterval);
}
render() {
return (
<div className="chart">
<Pie size="400" data={this.state.dataPie} percent={this.state.percent}></Pie>
</div>
);
}
}
export default RingChart;
Here my second component which create my pie chart :
import React from 'react';
class Pie extends React.Component {
constructor(props) {
super(props);
}
componentDidUpdate() {
let size = this.props.size;
const data = this.props.data;
let percent = this.props.percent;
console.log(data);
var margin = {top: 20, right: 20, bottom: 20, left: 20};
let width = size - margin.left - margin.right;
let height = width - margin.top - margin.bottom;
var chart = d3.select(".ring")
.append('svg')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + ((width/2)+margin.left) + "," + ((height/2)+margin.top) + ")");
var radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#313F46", "#4DD0E1"]);
var arc = d3.svg.arc()
.outerRadius(radius)
.innerRadius(radius - 20);
var pie = d3.layout.pie()
.sort(null)
.startAngle(1.1*Math.PI)
.endAngle(3.1*Math.PI)
.value(function(d) { return d.value; });
var g = chart.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("className", "arc");
function tweenPie(b) {
var i = d3.interpolate({startAngle: 1.1*Math.PI, endAngle: 1.1*Math.PI}, b);
return function(t) { return arc(i(t)); };
}
g.append("path")
.attr("fill", function(d, i) { return color(i); })
.transition()
.ease("exp")
.duration(600)
.attrTween("d", tweenPie);
g.append("text")
.attr("dy", ".35em")
.style("text-anchor", "middle")
.style("color", "#263238")
.style("font-size", "55px")
.attr("className", "text-pie")
.text(function(d) { return percent+'%'; });
}
render() {
return (<div className="ring" style={{textAlign:'center'}}></div>)
}
}
export default Pie;
My problem :
Nothing is created ... The console.log(data) in my second component return firstly an empty array and secondly my array with the correct values. It's weird that nothing is created, but even if it was created, my component Pie is call twice.
How can I call my component only once ?
Why my component is not created ?
How can I do for that my pie chart will be update automatically when new values appears on my server ?
thank's a lot, I'm discovering react.js, a lot of basic's notions is unknow for me.
EDIT : Ok now my component is created, I had forgotten the ".ring" in my line : var chart = d3.select("ring") -_-.
But as I said before, now two component are created (one empty, and one correct). Sometimes I have two components created correctly. It depends on the AJAX call ... How can I solve the problem of the async AJAX call ?

React basically takes the props and state of a component and when these changes it updates the component (it executes the render function again and then diffs this with your actual DOM).
You tap into the lifecycle with the ComponentDidUpdate method. This will be called every time React updates a component. Therefore, the first time it's called your array is empty (your ajax call hasn't returned). Then your call returns, which results in the array being filled. React updates your component and you get the updated data. The fact you see the log twice is simply because, according to React, your component should be updated: Once with an empty array, and once with the results in the filled array.
To see if you actually create two components, check out either the constructor or the ComponentDidMount methods, logging in there should only be called once per intended creation of the component.
To summarize: it's no problem that the ComponentDidUpdate call is called more than once.

Related

Why does my D3 legend color ramp replace my second legends colors when I remove it in react?

I am creating a React application and have a component that creates D3 legends for every layer I have selected. When I have multiple legends showing and one gets deselected, the remaining legend has its color ramp replaced by the colors of the removed legend. I would expect my remaining legend to have its proper color ramp.
import React, { useRef, useEffect } from 'react';
import PropTypes from 'prop-types';
import * as d3 from "d3";
function formatColors(colors, numberStops) {
let colorOffsets = [];
colors.forEach((colorHex, index) => {
const colorPct = ((index / numberStops) * 100.0).toFixed(2);
colorOffsets.push(
{offset: `${colorPct}%`, color: colorHex}
);
});
return colorOffsets;
}
const D3Legend = (props) => {
const d3Container = useRef(null);
useEffect(() => {
if(props.serviceType && d3Container.current){
const numberStops = props.legendStyle[props.serviceType].colors.length - 1;
const colors = props.legendStyle[props.serviceType].colors;
const colorOffsets = formatColors(colors, numberStops);
const svg = d3.select(d3Container.current);
const defUpdate = svg.selectAll("defs").data([1]);
const defEnter = defUpdate.enter().append("defs");
defEnter.append("linearGradient");
const linearGradient = defUpdate.merge(defEnter);
linearGradient
.select("linearGradient")
.attr("id", `linear-gradient-${props.serviceType}`)
.selectAll("stop")
.data(colorOffsets)
.enter().append("stop")
.attr("offset", function(d) { return d.offset; })
.attr("stop-color", function(d) { return d.color; });
defUpdate.exit().remove();
const update = svg.selectAll("g").data([1]);
const enter = update.enter().append("g");
enter.append("rect");
const bar = update.merge(enter);
bar
.select("rect")
.attr("width", 220)
.attr("height", 15)
.style("fill", `url(#linear-gradient-${props.serviceType})`);
update.exit().remove();
}
}
},
//React Hook useEffect has an unnecessary dependency: 'd3Container.current'.
//Either exclude it or remove the dependency array. Mutable values like
//'d3Container.current' aren't valid dependencies because mutating them
//doesn't re-render the component react-hooks/exhaustive-deps
//[props.serviceType, d3Container.current])
[props.serviceType])
return (
<>
<svg
className={"d3-legend " + props.legendStyle[props.serviceType].type}
ref={d3Container}
/>
<span className="d3-x-axis">Low</span>
<span className="d3-x-axis">Med</span>
<span className="d3-x-axis">High</span>
</>
);
}
D3Legend.propTypes = {
serviceType: PropTypes.string.isRequired,
legendStyle: PropTypes.object.isRequired,
}
export default D3Legend;
Below is the html elements when both legends are toggled (left) and when deselecting the top one (right).
The stop-colors from the deselected layer / legend end up replacing the ones for the still active layer / legend even though the correct legend name and ID is present.
I think it is related to how D3 binds the data and selects elements but I can not seem to work it out.
Update
Adding svg.selectAll("*").remove();
below const svg = d3.select(d3Container.current);
Has seemingly stopped this issue from happening. I don't quite understand why though...

D3 zoomable Sunburst and React update through props

I'm trying to get a react component working which uses the d3 sunburst chart. The problem I'm facing is I need a way to update the zoom level of the sunburst component, as a trigger from an external component. I'm sending the node to be zoomed to via the props to the sunburst component, and that changes each time there is an external input for a different component.
Here is the pseudocode I have so far but, each the the props changes.
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const SunburstSmooth = (props) => {
const prevProps = usePrevious(props);
useEffect(() => {
if (!isEqual(prevProps, props)) {
if (props.navigateTo) {
zoomToSunburst(props.navigateTo);
} else {
if (props.data) {
renderSunburstSmooth();
update();
}
}
}
}, [props])
// Global Variables
let root, node;
let gWidth, gHeight, radius, svg;
let color;
let x, y, arc, partition;
const svgRef = useRef();
const zoomToSunburst = (nodeToRender) => {
const gWidth = props.width;
const gHeight = props.height;
const radius = (Math.min(gWidth, gHeight) / 2) - 10
const svg = d3.select(svgRef.current)
const x = d3.scaleLinear().range([0, 2 * Math.PI])
const y = d3.scaleSqrt().range([0, radius])
const partition = d3.partition()
const arc = d3.arc()
// ....
root = d3.hierarchy(nodeToRender);
node = nodeToRender;
svg.selectAll("path")
.transition("update")
.duration(1000)
.attrTween("d", (d, i) =>
arcTweenPath(d, i, radius, x, y, arc));
}
const update = () => {
root.sum(d => d[props.value]);
let gSlices = svg.selectAll("g")
.data(partition(root).descendants())
.enter()
.append("g");
gSlices.exit().remove();
gSlices.append("path")
.style('fill', (d) => {
let hue;
const current = d;
if (current.depth === 0) {
return '#c6bebe';
}
return color((current.children ? current.x0 : current.parent.x0));
})
.attr('stroke', '#fff')
.attr('stroke-width', '1')
svg.selectAll("path")
.transition("update")
.duration(750)
.attrTween("d", (d, i) =>
arcTweenPath(d, i, radius, x, y, arc));
}
// Sets up the initial sunburst
const renderSunburstSmooth = () => {
// Structure
gWidth = props.width;
gHeight = props.height;
radius = (Math.min(gWidth, gHeight) / 2) - 10;
// Size our <svg> element, add a <g> element, and move translate 0,0 to the center of the element.
svg = d3.select(svgRef.current)
.append("g")
.attr("id", "bigG")
.attr("transform", `translate(${gWidth / 2},${gHeight / 2})`);
x = d3.scaleLinear().range([0, 2 * Math.PI]);
y = d3.scaleSqrt().range([0, radius]);
// Calculate the d path for each slice.
arc = d3.arc()
// .....
// Create our sunburst data structure
partition = d3.partition();
// Root Data
root = d3.hierarchy(props.data);
node = props.navigateTo || root;
}
return (
<div id={props.keyId}>
<svg ref={svgRef}/>
</div>
);
}
A lot of the code base code is from here:
http://bl.ocks.org/metmajer/5480307
Right now each time the prop is updated, the entire component is re-rendered. How do i make it so that it only updates the existing svg container, when the props.navigateTo is changed externally.
Right now, the rendering of your component depends on a change in any element in your props. In order to make it only depend on the change of the prop navigateTo, you would need two things:
1- navigateTo would need to be a state created with something like const [navigateTo, setNavigateTo] = UseState(""); in the parent component, and passed down as a prop. (I'm just putting this here to make sure you are doing this) So something like:
const parent = (props) => {
const [navigateTo, setNavigateTo] = UseState("");
<<any other code>>
return <SunburstSmooth
navigateTo={navigateTo}
data={data}
>
}
2- To make your code clearer, you can decompose the props to make the rendering depending on only a certain element of it:
const SunburstSmooth = (props) => {
const {navigateTo, data, ...rest} = props;
useEffect(() => {
if (data) {
renderSunburstSmooth();
update();
}
}, [navigateTo])
<<rest of your code>>
This ensures that the component is re-rendered only on changes of navigateTo, and not when something like data or any other prop is changed. In case you also want it to re-render every time data is changed, for example, you can just add it to the array at the end of the UseEffect hook
useEffect(() => {...}, [navigateTo, data])
Regarding the re-rendering of only the SVG element, any useEffect hook will cause everything in your return to be re-rendered, so the SVG would have to be the only thing your component returns in order to only re-render that. I can't see why you would mind re-rendering the enclosing div though

ReactJS and D3JS: setState

I'm trying to visualize a stock price chart, using reactJS and D3JS(v5).
Fetching data from Alphavantage API
Framework = ReactJS (which, honestly, introduces an unnecessary layer of complexity)
D3JS for viz
Roughly three parts to this:
global function parseData(). Parses data from the fetch into a
format that d3 can read. Verified working, but included for
completeness.
componentDidMount() call within class App. Promise chain: has fetch call, then parseData(), then setState(), and finally
drawChart()
drawChart() local function within class App: contains all D3 logic. Will be separated out into its own component later. Works if I pass it data from a local json (commented out; some sample rows provided below), but not when I try to pass it
data from fetch.
Code so far: this is all in App.js, no child components:
import React, { Component } from 'react';
import './App.css';
import * as d3 from "d3";
//import testTimeData from "./data/testTimeData"
function parseData(myInput) {
// processes alpha vantage data into a format for viz
// output an array of objects,
// where each object is {"Date":"yyyy-mm-dd", "a":<float>}
let newArray = []
for (var key in myInput) {
if (myInput.hasOwnProperty(key)) {
const newRow = Object.assign({"newDate": new Date(key)}, {"Date": key}, myInput[key])
newArray.push(newRow)
}
}
//console.log(newArray)
// 2. Generate plotData for d3js
let newArray2 = []
for (var i = 0; i < newArray.length; i++) {
let newRow = Object.assign({"Date": newArray[i]["Date"]}, {"a":parseFloat(newArray[i]["4. close"])})
newArray2.unshift(newRow)
}
return newArray2
}
class App extends Component {
constructor() {
super()
this.state = {
ticker:"",
plotData:[]
}
this.drawChart = this.drawChart.bind(this)
}
// setState() in componentDidMount()
// fetch monthly data
// hardcode ticker = VTSAX for the moment
// and call this.drawChart()
componentDidMount() {
const ticker = "VTSAX"
const api_key = "EEKL6B77HNZE6EB4"
fetch("https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol="+ticker+"&apikey="+api_key)
.then(console.log("fetching..."))
.then(response => response.json())
.then(data => parseData(data["Monthly Time Series"]))
.then(data => console.log(data))
.then(data =>{this.setState({plotData:data, ticker:ticker})})
.then(this.drawChart());
}
drawChart() {
const stockPlotData = this.state.plotData
console.log("stockPlotData.length=", stockPlotData.length)
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 300)
var margin = {left:50, right:30, top:30, bottom: 30}
var width = svg.attr("width") - margin.left - margin.right;
var height = svg.attr("height") - margin.bottom - margin.top;
var x = d3.scaleTime().rangeRound([0, width]);
var y = d3.scaleLinear().rangeRound([height, 0]);
var parseTime = d3.timeParse("%Y-%m-%d");
x.domain(d3.extent(stockPlotData, function(d) { return parseTime(d.date); }));
y.domain([0,
d3.max(stockPlotData, function(d) {
return d.a;
})]);
var multiline = function(category) {
var line = d3.line()
.x(function(d) { return x(parseTime(d.date)); })
.y(function(d) { return y(d[category]); });
return line;
}
var g = svg.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var categories = ['a'];
for (let i in categories) {
var lineFunction = multiline(categories[i]);
g.append("path")
.datum(stockPlotData)
.attr("class", "line")
.style("stroke", "blue")
//.style("stroke", color(i))
.style("fill", "None")
.attr("d", lineFunction);
}
// append X Axis
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickFormat(d3.timeFormat("%Y-%m-%d")));
// append Y Axis
g.append("g")
.call(d3.axisLeft(y));
}
render(){
return (<div>{this.state.ticker}</div>)
}
}
export default App;
Outputs from console.log() calls:
data => console.log(data) within the promise chain of componentDidMount() correctly displays the fetched data
But console.log("stockPlotData.length=", stockPlotData.length) call in the drawChart() function returns stockPlotData.length= 0. Did I call this.setState() wrongly?
The page renders "VTSAX" correctly in the render(){return(...)} call at the bottom, indicating that this.setState updated the ticker variable correctly.
Some test data rows:
[
{"Date":"2016-01-15", "a": 220 },
{"Date":"2016-01-16", "a": 250},
{"Date":"2016-01-17", "a": 130},
{"Date":"2016-01-18", "a": 180},
{"Date":"2016-01-19", "a": 200},
]
That's a lot of code to read, but based on a quick glance, you probably want to call this.drawChart() inside of a .then instead of after your promise chain, because it's gonna fire before your promises resolve.

Dashed line doesn't work

I have a list of radio buttons indicating a country.
I want that when user clicks on a radio button, the right line chart are loaded.
To do so I have 4 csv files with data for each country.
The csv files are like this:
year,death,value
2012,Tuberculosis,NA
2011,Tuberculosis,NA
2010,Tuberculosis,301
2009,Tuberculosis,344
2008,Tuberculosis,333
2007,Tuberculosis,329
2006,Tuberculosis,350
2005,Tuberculosis,NA
2004,Tuberculosis,NA
2003,Tuberculosis,396
2002,Tuberculosis,413
2001,Tuberculosis,415
2000,Tuberculosis,460
1999,Tuberculosis,517
1998,Tuberculosis,558
1997,Tuberculosis,597
1996,Tuberculosis,609
1995,Tuberculosis,647
2012,Tetanus,NA
2011,Tetanus,NA
2010,Tetanus,17
2009,Tetanus,27
2008,Tetanus,18
...
HERE is my code.
I don't know why Plunker create the error "Bad request".
Anyway, this is my result: if the user clicks on Italy, then the browser loads Italy.csv file and creates the chart, if the user clicks on Hungary, then browser loads Hungary.csv file and creates the chart and so on.
The problem is that in my csv files, there are some "holes" on data.
For example, I haven't any data about number of deaths in Italy between 2003 and 2006 so I want to show a dashed line instead of a solid line.
In my chart I'm not able to do that.
In particular, I wrote code to do that but it doesn't work.
Note that the missing data will change according to the countries. For some countries I have all the data and not for other countries.
Also, if the user clicks on the circle (the legend) of the corresponding series disappears, the circle should turn white and the axes change to fit the current data.
This doesn't work and I don't understand why.
As you can see, the console doesn't show any errors.
In this similar example, it works: PLUNKER
I badly explained myself. With "missing data" I mean NA values.
NEW PLUNKER
I understand that the chart of Belgium is right.
I tried to understand why the Denmark chart is that but sorry, I don't understand when you say "Code generate multiples paths for every abailable data and just ONE path for all NA data. Instead it must generate a segments for every gap on data.".
The code is organized in this way:
// some code...
/**************************************************************************
*** GET SELECTED COUNTRY AND DRAW FIRST CHART ****************************
**************************************************************************/
var l_selectedCountry = document.querySelector('input[name="l_country"]:checked').value;
l_createLineChart(l_selectedCountry);
var l_updateradio = function() {
l_selectedCountry = $('input[name=l_country]:checked', '#l_countries').val();
l_createLineChart(l_selectedCountry);
}
$("#l_countries").on("change", l_updateradio);
/**************************************************************************
*** DRAW THE RIGHT CHART BASED ON SELECTED COUNTRY **********************
**************************************************************************/
function l_createLineChart(l_selectedCountry) {
// remove last chart
d3.select("#l_chartcontainer")
.html("");
// adds the svg canvas
var svg = d3.select("#l_chartcontainer")
.append("svg")
.attr("width", l_width + margin.left + margin.right)
.attr("height", l_height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var l_file = "./data/line_data/" + l_selectedCountry + ".csv";
/**************************************************************************
*** GET RIGHT DATA ********************************************************
**************************************************************************/
d3.csv(l_file, function(l_error, l_data) {
l_data.forEach(function(d) {
d.year = parseDate(d.year);
d.value = +d.value;
});
// code about axis...
/**************************************************************************
*** GET ALL LINE DATA (solid & dashed) ************************************
**************************************************************************/
var l_dataNest = d3.nest()
.key(function(d) {
return d.death;
})
.entries(l_data);
console.log("l_dataNest");
console.log(l_dataNest);
// code about labels...
/**************************************************************************
*** GET DASHED LINE DATA **************************************************
**************************************************************************/
var l_dashedData = l_getDashed(l_dataNest); // dashed line (extreme point) for each disease
console.log("l_dashedData");
console.log(l_dashedData);
// other code...
/**************************************************************************
*** DRAW SOLID LINE *******************************************************
**************************************************************************/
svg.append('g')
.attr('class', 'l_line-container')
.selectAll('.normal-line-paths')
.data(l_dataNest) // set our data
.enter() // enter the data and start appending elements
.append('path')
.call(l_path); // calling our path function for later use when appending lines
/**************************************************************************
*** DRAW DASHED LINE ******************************************************
**************************************************************************/
svg.append('g')
.attr('class', 'dashed-line-container')
.selectAll('.dashed-line-paths')
.data(l_dashedData)
.enter()
.append('path')
.call(l_dashedPath);
// other code...
});
}
// code about event...
function l_path(myPath) {
myPath.style("fill", "none")
.style("stroke", function(d) {
return l_color[d.key];
})
.attr('class', function(d) {
return 'normal-line-paths path-' + d.key.replace(/\s+/g, '');
})
.attr("d", function(d) {
return valueline(d.values);
})
.style("opacity", 0.5)
.on("mouseover", l_onMouseOverLine)
.on("mouseout", l_onMouseOutLine);
}
function l_dashedPath(myPath) {
myPath.style("fill", "none")
.style("stroke", function(d) {
return l_color[d.key];
})
.style("stroke-width", 5)
.attr("stroke-dasharray", '4')
.attr('class', function(d) {
return 'dashed-line-paths path-' + d.key.replace(/\s+/g, '');
})
.attr("d", function(d) {
return valueline(d.values);
})
.style("opacity", 0.5)
.on("mouseover", l_onMouseOverLine)
.on("mouseout", l_onMouseOutLine);
}
/**
* Function to return the data points that will create the dashed lines.
*/
function l_getDashed(data) {
return data.map(function(collection, index) {
var l_startNaN = false;
var l_dashed = {
key: collection.key, // disease
values: [] //array of death, death and year
};
collection.values.forEach(function(dataPoint, index) {
var l_value = dataPoint.value;
var values = collection.values;
if($.isNumeric(l_value) && l_startNaN) {
l_startNaN = false;
l_dashed.values.push(values[index]);
}
else {
if(($.isNumeric(l_value) && l_startNaN) || (!$.isNumeric(l_value) && !l_startNaN)) {
l_startNaN = true;
l_dashed.values.push(values[index-1]);
}
}
})
if((l_dashed.values.length % 2)) {
l_dashed.values.pop();
}
return l_dashed;
});
}
If I comment this piece of code:
// apend a group element which will contain our dashed lines
/*svg.append('g')
.attr('class', 'dashed-line-container')
.selectAll('.dashed-line-paths')
.data(l_dashedData)
.enter()
.append('path')
.call(l_dashedPath);*/
I get:
and if I comment:
// apend a group element which will contain our lines
/*svg.append('g')
.attr('class', 'l_line-container')
.selectAll('.normal-line-paths')
.data(l_dataNest) // set our data
.enter() // enter the data and start appending elements
.append('path')
.call(l_path); */
I get:
So there must be a problem in l_dashedPath(myPath) method.
But if I print d, I get the four ends of the two portions dashed (Tuberculosis): 23, 32, 15, 16 that are correct.
.attr("d", function(d) {
console.log(d);
return valueline(d.values);
})
Also the l_getDashed(data) method seems correct to me.
Inside Plunker you can't have spaces on filename. Rename it 'United Kingdom.csv' to something with underscore (_) or so. "United_Kingdom.csv"
Your function l_getdashed, don't take the next point for every dashed segment. At the end you just get a point, not a segment.
I'ver re-refactory your function:
function l_getDashed(data) {
return data.map(function(collection, index) {
var l_startNaN = false;
var l_dashed = {
key: collection.key,
values: []
};
collection.values.forEach(function(dataPoint, index) {
var l_value = dataPoint.value;
var values = collection.values;
if ($.isNumeric(l_value) && l_startNaN) {
l_startNaN = false;
l_dashed.values.push(values[index]);
} else if ( !$.isNumeric(l_value) !== l_startNaN) {
l_startNaN = true
l_dashed.values.push(values[index-1]);
}
})
if (l_dashed.values.length % 2) { l_dashed.values.pop() }
return l_dashed;
});
}
Here the update working Plunker
DENMARK
Your code has concept errors. Thats why you see 'Denmark' like that.
Graphically:
Code generate multiples paths for every abailable data and just ONE path for all NA data. Instead it must generate a segments for every gap on data.-
BELGIUM
Belguim is a different history. The graph is ok, your data has isolated points (red dots) and can not be dismiss.
You can not draw a dashed-line from 1997-2009 (black arrow) because you'll discarding data.

D3 graph not updating with uniform data

I'm working on a bar chart. Strangely enough, the bar chart works fine for unique data, but when updated with data that is exactly the same, the graph does not update. It is not a scale/spacing problem, as the rect's representing the data aren't being generated at all.
Here is a JSFiddle demonstrating my problem.
More information
The bar chart is given data in the following form:
data.push({
temperature: 10,
humidity: 20,
light: 30
});
The bar chart creates a separate bar for each attribute. The attributes are represented by this type object, to help with spacing and naming the bars:
// type objects
typeObject = {
temperature: {
string: "temperature", //type name
class: "temp", //css class for styling
initDis: 0, //defines where to place first bar
dis: 3, //defines space between the next temp bar
base: tempBase, //the <g> element base
color: "#A6E22E" //color
},
humidity: {
...
},
...
};
And, every time data is added, these type objects are iterated through and passed into the following function, to make a d3 'update pattern':
var createBarsForCategory = function (type) {
var bars;
//`data` has been updated to an array containing newly added data
bars = type.base.selectAll("rect")
.data(data, function(d) {
return d[type.string];
});
//general update transition
bars.transition().duration(500).attr("x", function(d, i) {
return i * (m.bar.w + m.bar.space) * type.dis + m.bar.w * type.initDis;
});
// enter
bars.enter()
.insert("rect")
.attr("class", type.class)
.attr("width", m.bar.w)
.attr("height", function (d) {
return 0;
})
.attr("x", function (d, i) {
return i * (m.bar.w + m.bar.space) * type.dis + m.bar.w * type.initDis;
})
.attr("y", function (d) {
return scales.yscale(0);
})
.transition().delay(100).duration(1000)
.attr("height", function (d) {
return scales.yscale(0) - scales.yscale(d[type.string]);
})
.attr("y", function (d) {
return scales.yscale(d[type.string]);
})
;
// remove
bars.exit().remove();
};
Thank you in advance for your time.
I have updated your fiddle http://jsfiddle.net/pyLh7tcn/33/ with the following code on lines 173-178:
bars = type.base.selectAll("rect")
.data(data);
Now both random and uniform data sets are plotting properly.
Hope this helps.

Categories