Proper data structure for D3 stacked bar chart - d3.js

I am trying to visualize this data in a stacked bar chart with D3 v4:
var data = [
{'id': '10', 'status': 'on', 'variant': 'A', 'value': '200'},
{'id': '10', 'status': 'on', 'variant': 'B', 'value': '500'},
{'id': '11', 'status': 'off', 'variant': 'A', 'value': '100'},
{'id': '12', 'status': 'on', 'variant': 'A', 'value': '600'},
...
]
All elements with the same key id should stack on the x axis, where value defines the height and status defines the color.
A data set may contain multiple elements with the same id but different status or variant, like id=10 in the example.
Which format would be suitable to stack this data? My approach was nesting it:
var nested = d3.nest()
.key(function(d) { return d.id; })
.entries(data);
From here I am not sure how to generate the y0 and y1 values.
Can I use d3.stack?

You could loop through the values array in each top-level object and keep a running total of value, which becomes y0 for each <rect>. y1 then is y0 + value.
Then bind each top-level object to a <g> tag representing the entire stack, translate that to the appropriate x position (assuming bars are vertical), and append <rect>s to each <g> with the values prop as data. Set the height and y position of the rect according to where in the stack it belongs. That might look like:
var groups = d3.selectAll("g")
.data(nested)
.enter().append("g");
groups.attr("transform","translate...");
var rects = groups.selectAll("rect")
.data(function(d) { return d.values })
.enter().append("rect");
rects
.attr("width",...)
.attr("height",...)
.attr("y",...)

Related

Recharts value at pointer to show in tooltip?

Is it possible in Recharts to show a Horizontal line at the Y location where the user has their mouse over and retrieve that Y value so we can display it on the Tooltip?
https://meridian.a2z.com/components/tooltip/?platform=react-web
I've been trying to do some research into how we could get the Y value on the graph where the mouse is hovering or clicked, but I'm having trouble seeing where we could even pull that out.
Any tips on attributes or components we could use to grab this data? Is it even something we have access to from the library?
To clarify, we're trying to get the value of the Y axis at where the user has their cursor over the graph.
So if the graph looks like this and the user has their mouse at the pink dot location, I would be trying to grab out the value of ~7000 - what the y value would be at that graph location
Edit:
Note about responsiveness:
If you want to make this responsive, just adjust the chartBounds based on the padding/margin you've applied to the chart component and you should be good to go.
If you're trying something more advanced and need the height and width to pass to the chart component for more calculations, the following article should help: https://www.pluralsight.com/tech-blog/getting-size-and-position-of-an-element-in-react/
NOTE: This is a bit of a hack and may not be a perfect solution but it should be enough to get you on the right track
You should be able to use the chartX and chartY fields from onMouseMove. Unfortunately, this is just the pixel value under the cursor but you should be able to translate it into the range you are using for your graph.
Here is an example put together using the SimpleLineChart example recharts has up. This should work if you just want to get the Y value under the user's cursor and can be extended to get the X value as well.
const {LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend} = Recharts;
const data = [
{name: 'Page A', uv: 4000, pv: 2400, amt: 2400},
{name: 'Page B', uv: 3000, pv: 1398, amt: 2210},
{name: 'Page C', uv: 2000, pv: 9800, amt: 2290},
{name: 'Page D', uv: 2780, pv: 3908, amt: 2000},
{name: 'Page E', uv: 1890, pv: 4800, amt: 2181},
{name: 'Page F', uv: 2390, pv: 3800, amt: 2500},
{name: 'Page G', uv: 3490, pv: 4300, amt: 2100},
];
//The pixel bounds for the LineChart, 0,0 is the top left corner
// these were found using the inspector built into the web browser
// these are in pixels but correspond to the values used in your graph
// so 246 is 0 Y on the graph and 5 is 10000 Y on the graph (according to your data)
const chartBoundsY = {min: 246, max: 5}
// The bounds we are using for the chart
const chartMinMaxY = {min: 0, max: 10000}
// Convert the pixel value from the cursor to the scale used in the chart
const remapRange = value => {
let fromAbs = value - chartBoundsY.min
let fromMaxAbs = chartBoundsY.max - chartBoundsY.min
let normal = fromAbs / fromMaxAbs
let toMaxAbs = chartMinMaxY.max - chartMinMaxY.min
let toAbs = toMaxAbs * normal
return Math.ceil(toAbs + chartMinMaxY.min)
}
const SimpleLineChart = React.createClass({
render () {
return (
<LineChart
width={600} height={300} data={data}
margin={{top: 5, right: 30, left: 20, bottom: 5}}
onMouseMove={props => {
// We get the values passed into the onMouseMove event
if(props.isTooltipActive) {
// If the tooltip is active then we display the Y value
// under the mouse using our custom mapping
console.log(remapRange(props.chartY))
}
}}
>
<XAxis dataKey="name"/>
<YAxis/>
<CartesianGrid strokeDasharray="3 3"/>
<Tooltip/>
<Legend />
<Line type="monotone" dataKey="pv" stroke="#8884d8" activeDot={{r: 8}}/>
<Line type="monotone" dataKey="uv" stroke="#82ca9d" />
</LineChart>
)
}
})
ReactDOM.render(
<SimpleLineChart />,
document.getElementById('container')
)
You can open this example in jsfiddle and paste in the code above in the JS editor to try it out for yourself. http://recharts.org/en-US/examples
Here is the documentation for the mouse event for the LineChart: http://recharts.org/en-US/api/LineChart
This can be done with the axis scale option together with d3's invert method.
The following code excerpt should give you an idea.
const domainY = d3.extent(data, d => d[keyY])
const scaleY = d3.scaleLinear().domain(domainY).range([0, 1])
<AreaChart
onMouseDown={(e) => console.log(scaleY.invert(e.chartY))}
...
<YAxis
domain={['auto', 'auto']}
dataKey={keyY}
type="number"
scale={scaleY}
...

How to remove a paticular legend in D3

svgData = svg.select('g.data').selectAll('g.datum').data data, (d)-> d.key
newData = svgData.enter()
.append 'g'
.classed 'datum', true
legends = newData.append 'g'
.classed 'legend-entry', true
.attr 'opacity', 1
.on 'click', hidden.toggle
legends.append 'circle'
.attr 'r', 2
.attr 'cx', 1
.attr 'stroke-width', 1
.attr 'fill', (d)-> scales.color(d.key)
.attr 'stroke', (d)-> scales.color(d.key)
legends.append 'text'
.attr 'x', 5
.attr 'y', 2.5
.attr 'font-size', 7
.text (d)-> d.key
legends = svg.selectAll('g.legend-entry') #separate selection to get both old and new for positioning <br/>
Here svgData is the old data which is appearing on the legend and newData is data which I'm going to append to the legend. Before appending the newData I want to remove a few legends. The data which I want to remove from legends is stored in a variable say removeLegend. I want to remove all these legends before append data from newdata. Any Help would be appreciated. Thanks
Where are the parentheses?
You just need to follow the selection binding schedule: update, enter and exit.
update: binding data and comparing
enter: if there are more data than legend items create new items
exit: if there are less data than legend items, then remove the old items(this is exactly you want).
This is the Mike Bostock's tutorial in detail.

Datatable 1.9.4 edit row with fnUpdate without redraw

I would like edita a row without redraw... I use https://legacy.datatables.net/ref#fnUpdate and the function work like this;
oTable.fnUpdate( 1, 2, 3, 4, 5);
{object|array|string}: Data to update the cell/row with
{node|int}: TR element you want to update or the aoData index
{int}: The column to update (set to undefined to update the whole row)
{bool} [default=true]: Redraw the table or not
{bool} [default=true]: Perform pre-draw actions or not
Example:
oTable.fnUpdate( 'Example update', 0, 0); // Update Single cell (0,0) and redraw
oTable.fnUpdate( 'Example update', 0, 0, false); // Update Single cell (0,0) and NOT redraw
oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], 1 ); // Update Row 1 and redraw
And if i want to update without redraw ???
oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], 1, ???, false ); // Update Row 1 and NOT redraw
Thanks
I find it...
oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], 1, undefined, false );
var rowIndex = $('#example').dataTable().fnGetPosition( element.closest("tr").get(0));
$('#example').dataTable().fnUpdate( 'Approved', rowIndex , 7,false);
$('#example').dataTable().fnUpdate( 'Approved', 2, 5,false);//for 3rd row 6 column
$('#example').dataTable().fnUpdate( 'rejected', 2, 6,false);//for 3rd row 7 column
or
$('#example').dataTable().fnUpdate( ['Approved',rejected'',...], 2, [0,1,...],false);//for 3rd row all columns
Updating the cell content without redrawing the table and without effecting the column width
$('#...').dataTable().fnUpdate(
'cell data',
<row #>,
<column #>,
false, // redraw as false
false // making predraw actions also false
);
worked for me

How to add labels to c3.js scatter plot graph?

Is it possible to add labels to scatter plot points in c3.js like in this google charts example?
https://google-developers.appspot.com/chart/interactive/docs/gallery/bubblechart#javascript
c3 doesn't support this currently - https://github.com/masayuki0812/c3/issues/481. But you can easily add the functionality - just loop through the chart series and points and add the labels as necessary.
var labels = [
['AA', 'BB', 'CC', 'DD', 'EE', 'FF', 'GG', 'HH'],
['ZA', 'ZB', 'ZC', 'ZD', 'ZE', 'ZF', 'ZG', 'ZH']
];
// series
var series = chart.internal.main
.selectAll('.' + c3.chart.internal.fn.CLASS.circles)[0];
// text layers
var texts = chart.internal.main
.selectAll('.' + c3.chart.internal.fn.CLASS.chartTexts)
.selectAll('.' + c3.chart.internal.fn.CLASS.chartText)[0]
series.forEach(function (series, i) {
var points = d3.select(series).selectAll('.' + c3.chart.internal.fn.CLASS.circle)[0]
points.forEach(function (point, j) {
d3.select(texts[i])
.append('text')
.attr('text-anchor', 'middle')
.attr('dy', '0.3em')
.attr('x', d3.select(point).attr('cx'))
.attr('y', d3.select(point).attr('cy'))
.text(labels[i][j])
})
});
Fiddle - http://jsfiddle.net/6phuuans/
Currently C3.js doesnt provide us with the option to add labels to a scatter plot chart. But the following method can be used to add responsive data labels:
After the chart is rendered (in the "onrendered" property of the chart), identify the data points ( tags) and add tags with the x and y coodinates picked from the relevant circle, as the tags sibling.
Code Snippet:
onrendered: function(){
// get the parent of the the <circles> to add <text as siblings>
var g = d3.selectAll('.c3-circles');
//Get all circle tags
var circles = d3.selectAll('circle')[0];
//go to each circle and add a text label for it
for(var i = 0; i < circles.length; i++){
//fetch x-coordinate
var x = $(circles[i])[0].cx;
//fetch y-coordinate
var y = $(circles[i])[0].cy;
//create and append the text tag
g.append('text')
.attr('y', y.baseVal.value - 15) // (-15) places the tag above the circle, adjust it according to your need
.attr('x', x.baseVal.value)
.attr('text-anchor', 'middle')
.attr('class', 'c3-text c3-text-'+i)
.text(data[i].<data point key>) // the text that needs to be added can be hard coded or fetched for the original data.
//Since I am using a JSON to plot the data, I am referencing it and using the key of the value to be shown.
}
}
This will add the label, but on resize , multiple data labels will be plotted. To handle that, on the charts resize, we must remove the previous data labels (in the "onresize" property of the chart).
Code Snippet:
onresize: function () {
$('.c3-shapes.c3-circles text').remove();
}

d3 selectAll().data() just returns data in first element. why

I am trying to get the data in all the matching element using d3. I have following code
d3.selectAll('svg').selectAll('.line').data()
what i expect is that it should return data in all the matching element. but it just return data in first matching element.
if i just do
d3.selectAll('svg').selectAll('.line')
this shows that it has 2 group element and its data property contains the data.
if i just do var line = d3.selectAll('svg').selectAll('.line'); line[0].data()it gives me error. as line[0] become a DOM element without any property
how to get data in all matching selection or am i not clear on how to use it.
This is the expected behaviour as the spec on selection.data(values) reads:
If values is not specified, then this method returns the array of data
for the first group in the selection.
That explains why you only get the data bound to the first group.
To access data bound to all groups returned by your selection you could use:
d3.selectAll('svg').selectAll('.line').each(function(d) {
// Within this function d is this group's data.
// Iterate, accumulate, do whatever you like at this point.
});
I can not see your code, therefore I will show you a working one:
// scene setup
// we generate 3 SVG tags inside the page <body>
var $svg;
for (var svg = 0; svg < 3; svg++) {
$svg = d3.select("body").append("svg:svg").attr({
width: 200,
height: 200,
id: "svg_" + svg
});
}
// multiple selection + data
// consider the array of colors as data
var array_of_colors = ["red", "yellow", "blue", "khaki", "gray", "green"];
d3.selectAll("svg").selectAll("line.dash").data(array_of_colors).enter()
.append("svg:line").attr({
x1: function(d){return(50 + Math.random() * 50);},// random coordinates
y1: function(d){return(50 + Math.random() * 50);},// random coordinates
x2: 150,
y2: 140,
"stroke-width": 2,
stroke: function(d) {
console.log(d);
return (d);
}
}).classed({
"dash": true
});
The code produces 6 lines (as size of data array) in each SVG element:
Looks like:

Resources