Failed to display lines in a line chart in D3js - javascript

I have three array of objects.
I want to create a chart with 3 lines, the Dates are the same for the three arrays. The Y for peakUsage and avgUsage are the same values so they are horizontal straight lines
This code show the Y and X Axises but failing with drawing the actual lines.
data: { x: Date; y: number }[],
peakUsage: { x: Date; y: number }[],
avgUsage: { x: Date; y: number }[]
const axisThickness = 60;
const width = (params.width || 500) - axisThickness;
const height = (params.height || 500) - axisThickness;
const { document } = new JSDOM('').window;
// Create D3 container with SVG element
const div = d3.select(document.body).append('div');
const svg = div
.attr('class', 'container')
.append('svg')
.attr('xmlns', 'http://www.w3.org/2000/svg')
.attr('width', width + axisThickness)
.attr('height', height + axisThickness);
const yScale = d3.scaleLinear().range([height, 0]);
yScale.domain([0, Math.max(...data.map((d) => d.y))]);
const xScale = d3
.scaleTime()
.range([0, width])
.domain(
d3.extent(data, function (d) {
return new Date(d.x);
}) as Date[]
);
const g = svg.append('g').attr('transform', `translate(${axisThickness},${axisThickness / 2})`);
g.append('g').attr('transform', `translate(0,${height})`).call(d3.axisBottom(xScale));
g.append('g')
.call(
d3
.axisLeft(yScale)
.tickFormat(function (d) {
return `${d}`; // Label text
})
.ticks(8)
)
.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', 6)
.attr('dy', '-5.1em')
.attr('text-anchor', 'end')
.attr('fill', 'black')
.text('Est. kWh');
g.selectAll('.line')
.enter()
.append('path')
.data(data)
.attr('fill', 'none')
.attr('stroke', 'green')
.attr('stroke-width', 1.5)
.attr('d', function (d) {
return d3
.line()
.x(function (d: any) {
return xScale(d.x);
})
.y(function (d: any) {
return yScale(d.y);
}) as any;
});
g.selectAll('.line')
.enter()
.append('path')
.data(peakUsage)
.attr('fill', 'none')
.attr('stroke', 'Orange')
.attr('stroke-width', 1.5)
.attr(
'd',
d3
.line()
.x(function (d: any) {
return xScale(d.x);
})
.y(function (d: any) {
return yScale(d.y);
}) as any
);
g.selectAll('.line')
.enter()
.append('path')
.data(avgUsage)
.attr('fill', 'none')
.attr('stroke', 'blue')
.attr('stroke-width', 1.5)
.attr(
'd',
d3
.line()
.x(function (d: any) {
return xScale(d.x);
})
.y(function (d: any) {
return yScale(d.y);
}) as any
);
What I am doing wrong?

This bit is in the wrong order:
g.selectAll('.line')
.enter()
.append('path')
.data(data)
It should be:
g.selectAll('.line')
.data(data)
.enter()
.append('path')
But that does not fix your issue. Since you are using 3 different arrays for each line individually (a better approach is using just one array with 3 inner arrays, but that's another issue...), you can simply declare the line generator...
const lineGenerator = d3.line()
.x(function (d: any) {
return xScale(d.x);
})
.y(function (d: any) {
return yScale(d.y);
})
... and then, for each path, do:
g.append('path')
.attr('fill', 'foo')
.attr('stroke', 'bar')
.attr('stroke-width', 'baz')
.attr('d', lineGenerator(pathDataHere))
//the data for each line -----^

Related

exit().remove() won't remove previous content js d3

var datapoints = svg.selectAll('indPoints').data(filtered_up);
datapoints
.enter()
.append('circle')
.merge(datapoints)
.attr('cx', function (d) {
return x(d.callType) - jitterWidth / 2 + Math.random() * jitterWidth;
})
.attr('cy', function (d) {
return y(d.time);
})
.attr('r', 1.5)
.style('fill', function (d) {
return myColor(+d.time);
});
datapoints
.transition() // and apply changes to all of them
.duration(1000);
datapoints.exit().remove();
The above code won't remove previous datapoints after I change the data (filtered_up) content.
edit:
var boxes = svg.selectAll('boxes').data(sumstat);
boxes
.enter()
.append('rect')
.transition()
.duration(2000)
.attr('x', function (d) {
return x(d.key) - boxWidth / 2;
})
.attr('y', function (d) {
return y(d.value.q3);
})
.attr('height', function (d) {
return y(d.value.q1) - y(d.value.q3);
})
.attr('width', boxWidth)
.attr('stroke', 'black')
.style('fill', 'blue');
boxes.exit().remove();
Same is happening for the code above. Is it something about the indPoints/boxes that are selected with the append?
Try to use:
.html("");
example
d3.select('#graph').html("");

D3 - v5 update pattern on multiple text elements

In the following code I tried to create a visualization for a market on which one buys per hour. I tried to follow v5s update pattern but it won't let me join two text different <text> elements. The last added join overwrites the first so 8
I've looked around but I can not find anything related to an update pattern for two of the same elements.
https://jsfiddle.net/itsahoax/gd2uew73/7/
const updateCircles = () => {
const circles = d3.select('svg')
.selectAll('circle');
circles
.data(dataPoints)
.join('circle')
.attr('cx', xPosition)
.attr('cy', canvasHeight)
.attr('r', circleRadius)
.attr('id', (d) => d.uniqueid)
.attr('fill', (d) => d.color);
const text = d3.select('svg')
.selectAll('text')
.data(dataPoints);
text
.join()
.attr('x', xPosition)
.attr('y', canvasHeight)
.attr('id', (d) => d.uniqueid)
.text((d) => d.description);
text
.join()
.attr('x', xPosition)
.attr('y', canvasHeight + 15)
.attr('id', (d) => d.uniqueid)
.text((d) => `${d.value} KwH`);
};
if (update === true) {
updateCircles();
} else {
const circles = selection.selectAll('circle')
.data(dataPoints, (d) => d.id);
const text = selection.selectAll('text')
.data(dataPoints);
circles
.enter().append('circle')
.attr('cx', xPosition)
.attr('cy', canvasHeight)
.attr('r', circleRadius)
.attr('id', (d) => d.uniqueid)
.attr('fill', (d) => d.color)
.merge(circles);
text
.enter().append('text')
.attr('x', xPosition)
.attr('y', canvasHeight)
.attr('id', (d) => d.uniqueid)
.merge(text)
.text((d) => d.description);
text
.enter().append('text')
.attr('x', xPosition)
.attr('y', canvasHeight + 15)
.attr('id', (d) => d.uniqueid)
.merge(text)
.text((d) => `${d.value} KwH`);
}
};
Do not use an element selector if you have multiple elements with different content with the same selector (e.g <text>). Add them class and use .selectAll('.className')
There is a working example using selection.join JSFiddle.
More information about selection.join here.
// render code
const circles = (selection, dataPoints, isUpdate) => {
const xPosition = (d, i) => +i * 180 + 100;
const updateCircles = (data) => {
const circles = d3.select('svg').selectAll('.circle-area').data(data);
circles
.join((enter) => {
enter
.append('circle')
.attr('class', 'circle-area')
.attr('cx', xPosition)
.attr('cy', canvasHeight)
.attr('r', circleRadius)
.attr('id', (d) => d.uniqueid)
.attr('fill', (d) => d.color);
}, (update) => {
update.attr('fill', (d) => d.color);
}, (exit) => {
exit.remove();
});
const descriptionText = d3.select('svg').selectAll('.kwh-description').data(data);
descriptionText
.join((enter) => {
enter
.append('text')
.attr('class', 'kwh-description')
.attr('x', xPosition)
.attr('y', canvasHeight)
.attr('id', (d) => `description-${d.uniqueid}`)
.text((d) => d.description);
}, (update) => {
update.text((d) => d.description);
}, (exit) => {
exit.remove();
});
const valueText = d3.select('svg').selectAll('.kwh-value').data(data);
valueText
.join((enter) => {
enter
.append('text')
.attr('class', 'kwh-value')
.attr('x', xPosition)
.attr('y', canvasHeight + 15)
.attr('id', (d) => `value-${d.uniqueid}`)
.text((d) => `${d.value} KwH`);
}, (update) => {
update.text((d) => `${d.value} KwH`);
}, (exit) => {
exit.remove();
});
};
if (isUpdate) {
console.log(dataPoints)
updateCircles(dataPoints);
}
};

How do I create a single "<g> = group" with the existing "<g> = groups" in D3.js

I'm newbie in "d3.js", and I'm trying to implement a "doughnut chart" with some description info in the right side, I have grouped each of them in groups like<g class="device-info">, and I want all the created groups <g class="device-info"> to be in one group <g class="device-desc">, but I'm facing trouble to do it, any help will be appreciated.
here is my code:
var colors = ["#249AFF", "#CFDCE5", '#76D3C1']
var data1 = {online: 1430,offline:342, test: 200};
var pie = d3.pie()
.value(function(data1) {return data1.value })
var data_ready = pie(d3.entries(data1))
var arc = d3.arc()
.innerRadius(60)
.outerRadius(70)
.startAngle(data_ready => data_ready.startAngle)
.endAngle(data_ready => data_ready.endAngle)
var svg = d3.select(".svg")
.attr('width', 350)
.attr('height', 220)
svg.selectAll('path')
.data(data_ready)
.enter()
.append('path')
.attr('d', arc)
.attr('fill', (d, i) => colors[i])
.attr('stroke', 'none')
.attr('transform', "translate(100,130)")
var groups = svg.selectAll(".groups")
.data(data_ready)
.enter()
.append("g")
.attr("class", "device-info")
.attr('transform', ( d, i ) => {
i *= 30
return `translate(90, ${i})`
})
groups.append('circle')
.attr('fill', (d,i) => colors[i])
.attr('transform', `translate(147, 0)`)
.attr('r','5')
.attr('cx', -20)
.attr("cy", ( d, i ) => {
return i * 19
})
groups.append('text')
.text(data1 => data1.data.key)
.attr('fill', function(d,i){ return(colors[i]) })
.attr('class', 'devices-status')
.attr('transform', `translate(150, 5)`)
.style('text-transform', 'capitalize')
.style('font-size', 14)
.attr("dx", -15 )
.attr("dy", ( d, i ) => i * 20)
groups.append('text')
.text(data1 => data1.data.value + " devices")
.attr('fill', 'white')
.style('font-size', 15)
.attr('class', 'devices')
.attr('transform', `translate(140, 5)`)
.attr("dx", -15 )
.attr("dy", ( d, i ) => {
i += 2;
return i * 12
})
svg.append("text")
.text('Devices by status')
.attr('transform' , "translate(10, 25)")
.style('text-transform', 'uppercase')
.style('fill', 'rgb(255,255,255)')
.style('font-size', 15)
svg.append("text")
.text(`Total devices ${1772}`)
.attr('transform' , "translate(10, 45)")
.style('text-transform', 'capitalize')
.style('fill', 'rgba(255,255,255, .6)')
.style('font-size', 13)
and here is a demo:
Click here
Just append the container group to the SVG, with the respective class...
var containerGroup = svg.append("g")
.attr("class", "device-desk");
And then append groups to that container group:
var groups = containerGroup.selectAll(".groups")
.data(data_ready)
.enter()
//etc...

Append text to animated bar chart in d3js

I am trying to add some text at the end of the bars of a d3js bar chart.
The bar chart has transition with a delay. The source code can be found here https://bl.ocks.org/deciob/ffd5c65629e43449246cb80a0af280c7.
Unfortunately, with my code below the text does not follow the bars and I am not sure what I am doing wrong.
I thought the append text should be placed in the drawBars function no?
function drawBars(el, data, t) {
let barsG = el.select('.bars-g')
if (barsG.empty()) {
barsG = el.append('g')
.attr('class', 'bars-g');
}
const bars = barsG
.selectAll('.bar')
.data(data, yAccessor);
bars.exit()
.remove();
bars.enter()
.append('rect')
.attr('class', d => d.geoCode === 'WLD' ? 'bar wld' : 'bar')
.attr('x', leftPadding)
.attr('fill', function (d) {return d.geoColor;})
bars.enter()
.append('text')
.attr('x', d => xScale(xAccessor(d)))
.attr('y', d => yScale(yAccessor(d)))
.text('Hello')
.merge(bars).transition(t)
.attr('y', d => yScale(yAccessor(d)))
.attr('width', d => xScale(xAccessor(d)))
.attr('height', yScale.bandwidth())
.delay(delay)
}
What I am trying to achieve is for the text to follow the bars (and also later for the text to be updated to another value).
Thanks for any kind of help.
Found the answer, for anyone wondering you need to create a new function (eg: drawText()) and call it in later just below where the drawBars() function is called:
function drawText(el, data, t) {
var labels = svg.selectAll('.label')
.data(data, yAccessor);
var new_labels = labels
.enter()
.append('text')
.attr('class', 'label')
.attr('opacity', 0)
.attr('y', d => yScale(yAccessor(d)))
.attr('fill', 'blue')
.attr('text-anchor', 'middle')
new_labels.merge(labels)
.transition(t)
.attr('opacity', 1)
.attr('x', d => xScale(xAccessor(d))+50)
.attr('y', d => yScale(yAccessor(d)))
.text(function(d) {
return d.value;
});
labels
.exit()
.transition(t)
.attr('y', height)
.attr('opacity', 0)
.remove();
}

D3: Stacked bar chart exit().remove() not removing bars

I have the following enter / update / exit phases defined.
// this.x = my x time scale
// this.y = my y scale
// this.c = a color scale with 2 colors (red,blue)
// this.chart = D3.select() element
let series = D3.stack().keys(['point', 'topPoint'])(<any[]>this.barData);
this.chart
.append('g')
.selectAll('g')
.data(series)
.enter().append('g')
.attr('class', (d) => {return d.key + ' layer';})
.attr('fill', (d) => {return this.c(d.key);})
.selectAll('.bar')
.data((d) => {return d;})
.enter()
.append('rect')
.attr('class', 'bar');
// Update Phase
this.chart.selectAll('.bar').transition()
.attr('x', (d) => {return this.x(this._parseTime(d.data.date));})
.attr('y', (d) => {return this.y(d[1]); })
.attr('height', (d) => {return this.y(d[0]) - this.y(d[1]);})
.attr('width', 15);
// Exit phase
this.chart.selectAll('.point.layer').selectAll('.bar').exit().remove();
this.chart.selectAll('.topPoint.layer').selectAll('.bar').exit().remove();
When the data changes, the new bars are drawn, but they are drawn over the old bars.
if you use d3 v4 try this:
let series = D3.stack().keys(['point', 'topPoint'])(<any[]>this.barData);
const elements = this.chart
.append('g')
.selectAll('g')
.data(series);
elements.enter().append('g')
.attr('class', (d) => {return d.key + ' layer';})
.attr('fill', (d) => {return this.c(d.key);})
.each(function(d){
d3.select(this)
.append('rect')
.attr('class', 'bar');
})
.merge(elements) // updatePhase
.each(function(d){
d3.select(this).select(".bar")
.transition()
.attr('x', (d) => {return this.x(this._parseTime(d.data.date));})
.attr('y', (d) => {return this.y(d[1]); })
.attr('height', (d) => {return this.y(d[0]) - this.y(d[1]);})
.attr('width', 15);
}
// Exit phase
elements.exit().remove();
So the problem was my selecting of the elements I wish to bind and unbind.
this.chart
.selectAll('.layer')
.data(series)
.enter()
.append('g')
.attr('class', (d) => {return d.key + ' layer';});
// Set the enter phase for the bars within the groups, with the data derived from the layer data binding
this.chart.selectAll('.layer')
.selectAll('.bar')
.data((d) => {return d;})
.enter()
.append('rect')
.attr('class', 'bar');
// Set the update phase for the layers to fill the groups with the relevant color
let layers = this.chart.selectAll('.layer').attr('fill', (d) => {return this.c(d.key);});
// Update Phase
let bars;
if(this.animate) {
// Set the update phase of the bar data based on the data derived from the layer update phase
bars = layers.selectAll('.bar').data((d) => {return d;}).transition();
} else {
bars = layers.selectAll('.bar').data((d) => {return d;});
}
// Set the update phase of the bar data based on the data derived from the layer update phase
bars.attr('x', (d) => {return this.x(this._parseTime(d.data.date));})
.attr('y', (d) => {return this.y(d[1]); })
.attr('height', (d) => {return this.y(d[0]) - this.y(d[1]);})
.attr('width', 15);
// Exit phase
this.chart.selectAll('.layer').data(series).exit().remove();

Categories