d3.js - join data update behavior wired - d3.js

This demo try to update data when click the graph. it use the join method but looks like data and axis have been overlapped after click. the previous drawing not been cleared!
console.clear()
click_to_update()
function click_to_update() {
var svg = d3.select('body').append('svg')
.attr('width',600)
.attr('height',400)
.style('border','5px solid red')
var frame = svg.append('g').attr('class','frame')
frame.append('g').attr('class','xaxis')
frame.append('g').attr('class','yaxis')
d3.select('svg').on('click', function(){
var data = fetch_data()
refresh_graph(data)
})
var data = fetch_data()
refresh_graph(data)
function refresh_graph(data){
var svg = d3.select('svg')
var colors = d3.scaleOrdinal(d3.schemeSet3)
var margin = {left: 40,top: 10, right:10,bottom: 60},
width = +svg.attr('width') - margin.left - margin.right,
height = +svg.attr('height') - margin.top - margin.bottom;
var g = d3.select('.frame')
.attr('transform',`translate(${margin.left},${margin.top})`)
var xrange = data.map(function(d,i) { return i; })
var x = d3.scalePoint()
.domain(xrange)
.range([0, width]);
var ymax = d3.max(data,d => d.value)
var y = d3.scaleLinear()
.domain([0,ymax])
.range([height, 0]);
var drawpath = function(d,i) {
var ax = x(i)
var ay = y(d.value)
var bx = x(i)
var by = y(0)
var path = ['M',ax,ay,'L',bx,by]
return path.join(' ')
}
var g1 = g.selectAll('path')
.data(data)
.join('path')
.attr('stroke','gray')
.attr('stroke-width',1)
.style('fill', (d, i) => colors(i))
.transition()
.attr('d', drawpath)
g.selectAll('circle')
.data(data)
.join('circle')
.attr('fill','red')
.attr('cx',(d,i) => x(i))
.attr('cy',(d,i) => y(d.value))
.attr('r',5)
var xaxis = d3.select('.xaxis')
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
var yaxis = d3.select('.yaxis')
.attr('transform','translate(-5,0)')
.call(d3.axisLeft(y));
}
function fetch_data(){
var num = parseInt(Math.random()*20) + 5
var data = d3.range(num).map(d => {
return {value:Math.random()}
})
return data
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>

Related

d3.js - rotate axis and boxes together

I would like to rotate the axis and the boxes location with text (location rotated but keep box not rotated), currently only the axis has been rotated!
test()
function test() {
var width = 800
var height = 600
var margin = 20
var svg = d3.select('body').append('svg')
.attr('width',width).attr('height',height)
.style('border','1px solid red')
var g = svg.append('g')
.attr("transform", `translate(${margin}, ${margin})`)
var data = [
{
pos:0,
name:'A'
},
{
pos:2,
name:'B'
},
{
pos:12,
name:'C'
},
{
pos:15,
name:'D'
},
{
pos:20,
name:'E'
},
{
pos:23,
name:'F'
},
{
pos:26,
name:'G'
},
];
var xranges = data.map(function(d){
return d.pos
})
var scalelinear = d3.scaleLinear()
.domain([d3.min(xranges), d3.max(xranges)])
.range([0, width-2*margin]);
var scalepoint = d3.scalePoint()
.domain(xranges)
.range([0, width-2*margin]);
var scale = scalepoint
var x_axis = d3.axisTop()
.scale(scale)
g.append("g")
.call(x_axis)
.attr("transform", `translate(0, 5) rotate(30)`)
var iw = 48
var ih = 80
var g1 = g.selectAll(null)
.data(data).enter()
.append('g')
.attr("transform", `translate(0, 5)`)
g1.append('rect')
.attr('x', d => scale(d.pos)-iw/2)
.attr('y', 0)
.attr('width', iw)
.attr('height', ih)
.attr('fill','none')
.attr('stroke','black')
g1.append('text')
.attr('x', d => scale(d.pos))
.attr('y', ih/2)
.text(d => d.name)
.attr('text-anchor','middle')
.attr('fill','none')
.attr('stroke','black')
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>
Apply rotate to the top-level group:
test()
function test() {
var width = 800
var height = 600
var margin = 20
var svg = d3.select('body').append('svg')
.attr('width',width).attr('height',height)
.style('border','1px solid red')
var g = svg.append('g')
.attr("transform", `translate(${margin}, ${margin}) rotate(30)`)
var data = [
{
pos:0,
name:'A'
},
{
pos:2,
name:'B'
},
{
pos:12,
name:'C'
},
{
pos:15,
name:'D'
},
{
pos:20,
name:'E'
},
{
pos:23,
name:'F'
},
{
pos:26,
name:'G'
},
];
var xranges = data.map(function(d){
return d.pos
})
var scalelinear = d3.scaleLinear()
.domain([d3.min(xranges), d3.max(xranges)])
.range([0, width-2*margin]);
var scalepoint = d3.scalePoint()
.domain(xranges)
.range([0, width-2*margin]);
var scale = scalepoint
var x_axis = d3.axisTop()
.scale(scale)
g.append("g")
.call(x_axis)
.attr("transform", `translate(0, 5)`)
var iw = 48
var ih = 80
var g1 = g.selectAll(null)
.data(data).enter()
.append('g')
.attr("transform", `translate(0, 5)`)
g1.append('rect')
.attr('x', d => scale(d.pos)-iw/2)
.attr('y', 0)
.attr('width', iw)
.attr('height', ih)
.attr('fill','none')
.attr('stroke','black')
g1.append('text')
.attr('x', d => scale(d.pos))
.attr('y', ih/2)
.text(d => d.name)
.attr('text-anchor','middle')
.attr('fill','none')
.attr('stroke','black')
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js"></script>

D3 V3 Multi-line Chart - Issues appending lines to svg

I'm having issues getting D3v4 to show lines on a chart. I might be getting v3/v4 syntax confused.
I have the data nested as there are 5 lines.
// Chart Canvas Dimentions
var margin = {top: 20, right: 80, bottom: 30, left: 50};
var width = 900;
var height = 600;
// Time Parse
var parseTime = d3.time.format("%Y-%m-%d %H:%M:%S");
// Chart Axis Sizes
yAxisMax = Math.max.apply(Math, data.map(function(o){return o.value;})) * 1.1;
yAxisMin = Math.min.apply(Math, data.map(function(o){return o.value;})) - (this.yAxisMax * 0.1);
xAxisMax = width * 0.99;
console.log('yAxisMax: '+yAxisMax);
console.log('yAxisMin: '+yAxisMin);
console.log('xAxisMax: '+xAxisMax);
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
chartLine = d3.svg.line()
.x(function(d){ return x(parseTime(d.date)) })
.y(function(d){ return y(d.value) })
.interpolate("basis");
// Nest Entries by Name (Groups the Lines by Names - Seperate Entities)
var nestedData = d3.nest()
.key(function(d) { return d.name; })
.entries(data);
// D3 Chart - This is the Context to Work With
var context = d3.select("#chartContainer").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.attr("id", "D3lineChart")
.attr("class", "D3EventScopeContainer")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Interactive HoverLine
var hoverLine = context
.append('g')
.attr('class', 'hoverLineGroup')
.append("line")
.attr('transform', 'translate(70,0)')
.attr('class', 'interactiveHoverLine hidden')
.attr("x1", 0).attr("x2", 0)
.attr("y1", 0).attr("y2", height);
// Loop through data
nestedData.forEach(function(d,i) {
console.dir(d)
console.dir(d.values)
// Add Line
context
.append('g')
.attr('class', 'lineGroup')
.append('path')
.attr('transform', 'translate(70,0)')
.attr('class', 'chartLinesGroup tag'+ d.key.replace(/\s+/g, '').replace('.', '').replace('-', '').toLowerCase())
.style("stroke", function() { return d.color = color(d.key); }) // Add the colours dynamically
.style("stroke-opacity", 1)
//.attr('d', chartLine(d.values))
.on("mouseover", function() {
d3.select(this)
.style("stroke-width", 7.5)
})
.on("mouseout", function() {
d3.select(this)
.style("stroke-width", 2.5)
});
});
It fails when I enable the line
.attr('d', chartLine(d.values))
This function must not be formated correctly to use the data.
The error I get is - related to date processing:
Any advice would be greatly appreciated.
I'm essentially trying to get the the lines to show on the chart.
thanks
*** I get around the error message by adding .parse to the end of the time format line:
// Time Parse
var parseTime = d3.time.format("%Y-%m-%d %H:%M:%S").parse;
Still nothing showing on the screen - div/svg has height/width set...
hummmmm
You need to read API;) But at first u must try :
var x = d3.scaleTime()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var xAxis = d3.axisBottom(x).tickFormat(d3.timeFormat("%H:%M:%S.%L"));
var yAxis = d3.axisLeft(y);
parseTime = d3.timeParse("%Y-%m-%d %H:%M:%S.%L");
chartLine = d3.line()
.curve(d3.curveMonotoneX)
.x(function(d){ return x(parseTime(d.date)) })
.y(function(d){ return y(d.value) });
Hope its help

D3 version 4 AXIS

Does someone know why I can't see the axis of my line chart?
This is the link to the chart: https://d3responsive.firebaseapp.com/responsive.html
And this is the JavaScript code:
/* D3-v4 curve interpolation comparison: https://bl.ocks.org/d3noob/ced1b9b18bd8192d2c898884033b5529 */
var dataline1 = [
{"mes":1, "impuestoPorcentaje":20},
{"mes":2, "impuestoPorcentaje":14},
{"mes":3, "impuestoPorcentaje":20},
{"mes":4, "impuestoPorcentaje":21},
{"mes":5, "impuestoPorcentaje":15},
{"mes":6, "impuestoPorcentaje":22},
{"mes":7, "impuestoPorcentaje":9},
{"mes":8, "impuestoPorcentaje":6},
{"mes":9, "impuestoPorcentaje":23},
{"mes":10, "impuestoPorcentaje":7},
{"mes":11, "impuestoPorcentaje": 40},
{"mes":12, "impuestoPorcentaje": 45}
];
var dataline2 = [
{"mes":1, "impuestoPorcentaje":14},
{"mes":2, "impuestoPorcentaje":19},
{"mes":3, "impuestoPorcentaje":24},
{"mes":4, "impuestoPorcentaje":24},
{"mes":5, "impuestoPorcentaje":24},
{"mes":6, "impuestoPorcentaje":27},
{"mes":7, "impuestoPorcentaje":32},
{"mes":8, "impuestoPorcentaje":38},
{"mes":9, "impuestoPorcentaje":11},
{"mes":10, "impuestoPorcentaje":25},
{"mes":11, "impuestoPorcentaje": 40},
{"mes":12, "impuestoPorcentaje": 45}
];
var wl = 550;
var hl = 450;
var svgl = d3.select("body").append("svg")
.attrs({
width: wl,
height: hl
});
// Domain and ranges
var xscalel1 = d3.scaleLinear()
.domain([0, d3.max(dataline1, function(d) {
return d.mes;
})])
.range([0, wl - 30]);
var yscalel1 = d3.scaleLinear()
.domain([0, d3.max(dataline1, function(d) {
return d.impuestoPorcentaje;
})])
.range([hl - 30, 15]);
var xscalel2 = d3.scaleLinear()
.domain([0, d3.max(dataline2, function(d) {
return d.mes;
})])
.range([0, wl - 30]);
var yscalel2 = d3.scaleLinear()
.domain([0, d3.max(dataline2, function(d) {
return d.impuestoPorcentaje;
})])
.range([hl - 30, 15]);
// Lines
var lineOne = d3.line()
.x(function(d) {
return xscalel1(d.mes);
})
.y(function(d) {
return yscalel1(d.impuestoPorcentaje);
})
.curve(d3.curveLinear);
var lineTwo = d3.line()
.x(function(d) {
return xscalel2(d.mes);
})
.y(function(d) {
return yscalel2(d.impuestoPorcentaje);
})
.curve(d3.curveMonotoneX);
var vis = svgl.append("path")
.attrs({
d: lineOne(dataline1),
"stroke": "#008080",
"stroke-width": 2,
"fill": "none"
});
var vis2 = svgl.append("path")
.attrs({
d: lineTwo(dataline2),
"stroke": "orange",
"stroke-width": 2,
"fill": "none"
});
// Add the x Axis
svgl.append("g")
.attr("transform", "translate(0," + hl + ")")
.call(d3.axisBottom(xscalel1));
// Add the y Axis
svgl.append("g")
.call(d3.axisLeft(yscalel1));
You are translating the axis all the way down to the height of the SVG. You have to leave some margin.
For instance, this is your code right now (I'm simplifying your domain):
var wl = 550;
var hl = 150;
var svgl = d3.select("body").append("svg")
.attr("width", wl)
.attr("height", hl);
var xscalel1 = d3.scaleLinear()
.domain([0, 100])
.range([0, wl-30]);
svgl.append("g")
.attr("transform", "translate(0," + hl + ")")
.call( d3.axisBottom(xscalel1) );
<script src="https://d3js.org/d3.v4.min.js"></script>
Nothing will show up after clicking "run code snippet", just a blank space. You can't see anything, because this:
.attr("transform", "translate(0," + hl + ")")
Is moving the axis to the height (hl) of the SVG, that is, to its end.
Now let's see the same code with some margin, like this:
.attr("transform", "translate(0," + (hl - 20) + ")")
That way, we are moving the axis to 20 pixels before the end (height) of the SVG.
And here is the result, now you can see the axis:
var wl = 550;
var hl = 150;
var svgl = d3.select("body").append("svg")
.attr("width", wl)
.attr("height", hl);
var xscalel1 = d3.scaleLinear()
.domain([0, 100])
.range([0, wl-30]);
svgl.append("g")
.attr("transform", "translate(0," + (hl - 20) + ")")
.call( d3.axisBottom(xscalel1) );
<script src="https://d3js.org/d3.v4.min.js"></script>

color category in d3.js

histogram
I want to plot histogram using d3.js where i have dataset of around 13000 points which is divided into 2 clusters . I want to color both of them but when i use category color it only shows first one.In the input file i have Droplet_no, Amplitude, Cluster.
Here is my code :
<script type="text/javascript">
d3.csv("test_F06.csv",function type(d){
d.Droplet_no = +d.Droplet_no;
d.Amplitude = +d.Amplitude;
return d;} , function(data){
var width = 600;
height = 500;
padding = 50;
var colorColumn = "Cluster";
var map = data.map(function(i){return parseInt(i.Amplitude);})
var x = d3.scale.linear()
.domain([0, d3.max(map)])
.range([0, width]);
var xAxis = d3.svg.axis()
.scale(x);
var numbins = 3000;
var histogram = d3.layout.histogram()
.bins(x.ticks(numbins))
(map);
var y = d3.scale.linear()
.domain([0, d3.max(histogram.map(function(i){return i.length;}))])
.range([0, height/2]);
var colorScale = d3.scale.category10();
var canvas = d3.select("body").append("svg")
.attr("width", width+padding)
.attr("height", height+ padding)
.append("g")
.attr("transform", "translate(20,0)")
var bars = canvas.selectAll(".bar")
.data(histogram)
.enter()
.append("g")
.attr("class", "bar")
var group = canvas.append("g")
.attr("tansform","translate(0, " + height + ")")
bars.append("rect")
.attr("x", function(d){return x(d.x);})
.attr("y", function(d){return 500-y(d.y);})
.attr("width", function(d){return d.dx;})
.attr("height", function(d){ return y(d.y);})
.attr("fill", function(d){return colorScale(d[colorColumn]);});
})
</script>
Can anyone help me?
I am attaching image of the plot as well

d3 redraw bar chart with new values

My barchart draws fine when the page first loads.
But choose hour 2 from the drop-down, and it doesn't want to update to hour 2 data, it just keeps displaying hour 1.
FIDDLE
This is my d3 and js:
$('#bar_chart').css('overflow-x','scroll');
var margin = {top: 20, right: 20, bottom: 40, left: 80},
width = 220 - margin.left - margin.right,
height = 233 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, 1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient('bottom');
var formatComma = d3.format('0,000');
var yAxis = d3.svg.axis()
.scale(y)
.orient('left')
.ticks(5)
.outerTickSize(0)
.tickFormat(formatComma);
var svg = d3.select('th.chart-here').append('svg')
.attr('viewBox', '0 0 220 233')
.attr('preserveAspectRatio','xMinYMin meet')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left/1.5 + ',' + margin.top/1.5 + ')');
var table_i = 0;
var arr1 =
[
{'hour':1,'car':[{'audi':1377},{'bmw':716},{'ford':3819},{'mazda':67},{'toyota':11580},{'tesla':0}]},
{'hour':2,'car':[{'audi':9000},{'bmw':2000},{'ford':7000},{'mazda':1000},{'toyota':5000},{'tesla':700}]},
];
var hour = arr1[table_i];
var car=hour.car;
var newobj = [];
for(var hourx1=0;hourx1<car.length;hourx1++){
var xx = car[hourx1];
for (var value in xx) {
var chartvar = newobj.push({car:value,miles:xx[value]});
var data = newobj;
}
}
x.domain(data.map(function(d) { return d.car; }));
y.domain([0, d3.max(data, function(d) { return d.miles; })]);
svg.append('g')
.attr('class', 'y axis')
.call(yAxis)
.append('text')
.attr('y', 6)
.attr('dy', '.71em')
.style('text-anchor', 'start');
function changeHour(){
svg.selectAll('.bar')
.data(data)
.enter().append('rect')
.attr('class', 'bar')
.attr('transform','translate(-20)') //move rects closer to Y axis
.attr('x', function(d) { return x(d.car); })
.attr('width', x.rangeBand()*1)
.attr('y', function(d) { return y(d.miles); })
.attr('height', function(d) { return height - y(d.miles); });
xtext = svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(-20,' + height + ')') //move tick text so it aligns with rects
.call(xAxis);
xtext.selectAll('text')
.attr('transform', function(d) {
return 'translate(' + this.getBBox().height*50 + ',' + this.getBBox().height + ')rotate(0)';
});
//code to enable jqm checkbox
$('#checkbox-2a').on('change', function(e){
originalchange(e);
});
$( '#checkbox-2a' ).checkboxradio({
defaults: true
});
var sortTimeout = setTimeout(function() {
$('#checkbox-2a').prop('checked', false).checkboxradio( 'refresh' ).change();
}, 1000);
function originalchange() {
clearTimeout(sortTimeout);
var IsChecked = $('#checkbox-2a').is(':checked');
// Copy-on-write since tweens are evaluated after a delay.
var x0 = x.domain(data.sort(IsChecked
? function(a, b) { return b.miles - a.miles; }
: function(a, b) { return d3.ascending(a.car, b.car); })
.map(function(d) { return d.car; }))
.copy();
svg.selectAll('.bar')
.sort(function(a, b) { return x0(a.car) - x0(b.car); });
var transition = svg.transition().duration(950),
delay = function(d, i) { return i * 50; };
transition.selectAll('.bar')
.delay(delay)
.attr('x', function(d) { return x0(d.car); });
transition.select('.x.axis')
.call(xAxis)
.selectAll('g')
.delay(delay);
};
}
changeHour();
$('select').change(function() { //function to change hourly data
table_i = $(this).val();
var hour = arr1[table_i];
var car=hour.car;
var newobj = [];
for(var hourx1=0;hourx1<car.length;hourx1++){
var xx = car[hourx1];
for (var value in xx) {
var chartvar = newobj.push({car:value,miles:xx[value]});
var data = newobj;
}
}
x.domain(data.map(function(d) { return d.car; }));
y.domain([0, d3.max(data, function(d) { return d.miles; })]);
changeHour();
})
I thought that by updating in the function changeHour I could isolate just the rects and the text that goes with them, and redraw them based on the selected hour's data.
But it just keeps drawing the first hour.
What am I doing wrong?
2 things not working:
firstly "data" needs to be declared without 'var' in the change function at the end. Declaring it with 'var' makes it a local variable to that function, and once you leave that function it's gone. Saying "data = " without the var means you're using the data variable you've declared further up. It's all to do with scope which is something I still struggle with, but basically with 'var' it doesn't work.
var newobj = [];
for(var hourx1=0;hourx1<car.length;hourx1++){
var xx = car[hourx1];
for (var value in xx) {
var chartvar = newobj.push({car:value,miles:xx[value]});
}
}
data = newobj;
Secondly, your changeHour function only looks for new elements as it hangs all its attribute settings on an .enter() selection, changeHour should be like this:
var dataJoin = svg.selectAll('.bar')
.data(data, function(d) { return d.car; });
// possible new elements, fired first time, set non-data dependent attributes
dataJoin
.enter()
.append('rect')
.attr('class', 'bar')
.attr('transform','translate(-20)') //move rects closer to Y axis
// changes to existing elements (now including the newly appended elements from above) which depend on data values (d)
dataJoin
.attr('x', function(d) { return x(d.car); })
.attr('width', x.rangeBand()*1)
.attr('y', function(d) { return y(d.miles); })
.attr('height', function(d) { return height - y(d.miles); });
For completeness there should be a dataJoin.exit().remove() in there as well but its not something that happens in this dataset

Resources