I am using NVD3NVD3 to show a lineChart. The legend is very useful, and I want to enable the ability to only display a line for a single legend element, however the default configuration will transition the yAxis to be [min,max] of that legend element. I would like it to always display [0,max]. I found I can do this, however it will have a domain of 0 to max of all legend elements, not 0 to visible legened elements. Does anyone know if it is possible?
var chart = nv.models.lineChart();
var yMax = d3.max(data, function(d) {
return d3.max(d['values'], function(d2) {
return d2['open'];
});
});
chart.yDomain([0, yMax]);
UPDATE: I was able to achieve what I wanted with the following:
chart.dispatch.on('stateChange', function(e) {
var i = -1;
var yMax = d3.max(data, function(d) {
i++;
if(e.disabled[i] == false){
return d3.max(d['values'], function(d2) {
return(d2['open']);
});
}
});
chart.yDomain([0, yMax]);
});
Related
i'm using a rowchart to show the total of sales by item of a salesman.
Already tried a composite chart unsuccessfully like many posts from the google, but none of the examples uses a rowchart.
I need to do like the image, creating the red lines to represent the sale value target for each item, but i dont know how, can you guys help me? Thanks!
Actually this is my code to plot the rowchart
spenderRowChart = dc.rowChart("#chart-row-spenders");
spenderRowChart
.width(450).height(200)
.dimension(itemDim)
.group(totalItemGroup)
.elasticX(true);
Obviously you need a source for the target data, which could be a global map, or a field in your data.
I've created an example which pulls the data from a global, but it would also take from the data if your group reduction provides a field called target.
Then, it adds a new path element to each row. Conveniently the rows are already SVG g group elements, so anything put in there will already be offset to the top left corner of the row rect.
The only coordinate we are missing is the height of the rect, which we can get by reading it from one of the existing bars:
var height = chart.select('g.row rect').attr('height');
Then we select the gs and use the general update pattern to add a path.target to each one if it doesn't have one. We'll make it red, make it visible only if we have data for that row, and start it at X 0 so that it will animate from the left like the row rects do:
var target = chart.selectAll('g.row')
.selectAll('path.target').data(function(d) { return [d]; });
target = target.enter().append('path')
.attr('class', 'target')
.attr('stroke', 'red')
.attr('visibility', function(d) {
return (d.value.target !== undefined || _targets[d.key] !== undefined) ? 'visible' : 'hidden';
})
.attr('d', function(d) {
return 'M0,0 v' + height;
}).merge(target);
The final .merge(target) merges this selection into the main selection.
Now we can now animate all target lines into position:
target.transition().duration(chart.transitionDuration())
.attr('visibility', function(d) {
return (d.value.target !== undefined || _targets[d.key] !== undefined) ? 'visible' : 'hidden';
})
.attr('d', function(d) {
return 'M' + (chart.x()(d.value.target || _targets[d.key] || 0)+0.5) + ',0 v' + height;
});
The example doesn't show it, but this will also allow the targets to move dynamically if they change or the scale changes. Likewise targets may also become visible or invisible if data is added/removed.
thank you, due the long time to have an answer i've developed a solution already, but, really thank you and its so nice beacause its pretty much the same ideia, so i think its nice to share the code here too.
The difference its in my code i use other logic to clear the strokes and use the filter value of some other chart to make it dynamic.
.renderlet(function(chart) {
dc.events.trigger(function() {
filter1 = yearRingChart.filters();
filter2 = spenderRowChart.filters();
});
})
.on('pretransition', function(chart) {
if (aux_path.length > 0){
for (i = 0; i < aux_path.length; i++){
aux_path[i].remove();
}
};
aux_data = JSON.parse(JSON.stringify(data2));
aux_data = aux_data.filter(venda => filter1.indexOf(venda.Nome) > -1);
meta_subgrupo = [];
aux_data.forEach(function(o) {
var existing = meta_subgrupo.filter(function(i) { return i.SubGrupo === o.SubGrupo })[0];
if (!existing)
meta_subgrupo.push(o);
else
existing.Meta += o.Meta;
});
if (filter1.length > 0) {
for (i = 0; (i < Object.keys(subGrupos).length); i++){
var x_vert = meta_subgrupo[i].Meta;
var extra_data = [
{x: chart.x()(x_vert), y: 0},
{x: chart.x()(x_vert), y: chart.effectiveHeight()}
];
var line = d3.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.curve(d3.curveLinear);
var chartBody = chart.select('g');
var path = chartBody.selectAll('path.extra').data([extra_data]);
path = path.enter()
.append('path')
.attr('class', 'oeExtra')
.attr('stroke', subGruposColors[i].Color)
.attr('id', 'ids')
.attr("stroke-width", 2)
.style("stroke-dasharray", ("10,3"))
.merge(path)
path.attr('d', line);
aux_path.push(path);
}
}
})
And that's how it looks
Used DC.js to create stacked bar chart with ordinal x-axis.
Versions used:
DC.js version 1.7.5
crossfilter.js version 1.3.12
D3.js version 3.5.17
The problem is that the chart's x-axis labels are not aligned with bars. They are actually shifted two ticks to right so last two labels have no bars above them.
Edit to remove - Also can't select the right most bar to filter data eg hover over bar doesn't show selector to click and activate cross filter. - it was just two chart margins overlapping blocking cursor.
Here is screenshot of chart indicating problems.
The x-axis is ordinal set using .xUnits(dc.units.ordinal)
I used a renderlet to change x-axis label orientation so they are vertical. If I remove renderlet it doesn't change the problems above.
Here is my chart div and javascript code.
<div id="month-chart"></div>
<script type="text/javascript">
d3.csv("merged_hostname.csv", function(data) {
var parseDate = d3.time.format("%Y-%m-%d").parse;
data.forEach(function(d) {
d.date = parseDate(d.date);
d.sessions = +d.sessions;
d.ad_requests = +d.ad_requests;
d.bounceRate = +d.bounceRate;
d.clicks = +d.clicks;
d.earnings = +d.earnings;
d.newUsers = +d.newUsers;
d.sessionDuration = +d.sessionDuration;
d.sessionsPerUser = +d.sessionsPerUser;
d.twitterSessions = +d.twitterSessions;
d.users = +d.users;
});
var ndx = crossfilter(data);
var yyyymmDim = ndx.dimension(function(d) { return d["yyyymm"]; });
var PPCCByYYYYMM = yyyymmDim.group().reduceSum(function(d) {
if (d.PPCC === "PPCC") {
return +d.sessions;
}else{
return 0;
}
});
var otherByYYYYMM = yyyymmDim.group().reduceSum(function(d) {
if (d.PPCC === "Other") {
return +d.sessions;
}else{
return 0;
}
});
monthChart = dc.barChart("#month-chart");
monthChart
.height(200)
.width(500)
.margins({top: 10, right: 10, bottom: 50, left: 40})
.dimension(yyyymmDim)
.group(PPCCByYYYYMM)
.stack(otherByYYYYMM)
.transitionDuration(500)
.brushOn(true)
.elasticY(true)
.yAxisLabel('sessions')
.x(d3.scale.ordinal())
.xUnits(dc.units.ordinal)
.renderlet(function (chart) {
chart.selectAll("g.x text")
.attr('dx', '-30')
.attr('transform', "rotate(-90)");
});
dc.renderAll();
});
</script>
Any ideas what can causes these issues and how to resolve?
You can move the left position with this:
.attr('transform', "translate(-20,0) rotate(-90)");
Change 20 if its necessary
I want to use d3.chart() for the charts I have written already. I found examples of d3.chart() for circle and barcharts but not for line charts. My charts are line charts, I need to use following code in d3.charts()
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
but am facing problem when try to use like this
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="d3.v3.min.js"></script>
<script type="text/javascript" src="d3.chart.min.js"></script>
</head>
<body>
<div id="vis"></div>
<script type="text/javascript">
d3.chart("linechart", {
initialize: function() {
// create a base scale we will use later.
var chart = this;
chart.w = chart.base.attr('width') || 200;
chart.h = chart.base.attr('height') || 150;
chart.w = +chart.w;
chart.h = +chart.h;
chart.x = d3.scale.linear()
.range([0, chart.w]);
chart.y = d3.scale.linear()
.range([chart.h,0]);
chart.base.classed('line', true);
this.areas = {};
chart.areas.lines = chart.base.append('g')
.classed('lines', true)
.attr('width', chart.w)
.attr('height', chart.h)
chart.line = d3.svg.line()
.x(function(d) { return chart.x(d.x);})
.y(function(d) { return chart.y(d.y);});
this.layer("lines", chart.areas.lines, {
dataBind: function(data) {
// update the domain of the xScale since it depends on the data
chart.y.domain([d3.min(data,function(d){return d.y}),d3.max(data,function(d){return d.y})])
chart.x.domain(d3.extent(data, function(d) { return d.x; }));
// return a data bound selection for the passed in data.
return this.append("path")
.datum(data)
.attr("d", chart.line)
.attr('stroke','#1ABC9C')
.attr('stroke-width','2')
.attr('fill','none');
},
insert: function() {
return null;
},
});
},
// configures the width of the chart.
// when called without arguments, returns the
// current width.
width: function(newWidth) {
if (arguments.length === 0) {
return this.w;
}
this.w = newWidth;
return this;
},
// configures the height of the chart.
// when called without arguments, returns the
// current height.
height: function(newHeight) {
if (arguments.length === 0) {
return this.h;
}
this.h = newHeight;
return this;
},
});
var data = [
{x: 0,y:190},
{x: 1,y:10},{x: 2,y:40},{x: 3,y:90},
{x: 4,y:30},{x: 5,y:20},{x: 6,y:10}
];
var chart1 = d3.select("#vis")
.append("svg")
.chart("linechart")
.width(720)
.height(320)
chart1.draw(data);
</script>
</body>
</html>
error:
Uncaught Error: [d3.chart] Layer selection not properly bound.
I have get the line and error as well.
Note: Get d3.chart.min.js from this link
Get d3.v3.min.js from this link
Updated: I got answer from #LarsKotthoff answer, but there is different in image. check this links Before apply D3 and After apply D3.
It looks like you have confused the insert and dataBind actions -- in the former, you're supposed to append the new elements while the latter only binds the data. With the modifications below, your code works fine for me.
dataBind: function(data) {
// update the domain of the xScale since it depends on the data
chart.y.domain([d3.min(data,function(d){return d.y}),d3.max(data,function(d){return d.y})])
chart.x.domain(d3.extent(data, function(d) { return d.x; }));
// return a data bound selection for the passed in data.
return this.selectAll("path").data([data]);
},
insert: function() {
return this.append("path")
.attr("d", chart.line)
.attr('stroke','#1ABC9C')
.attr('stroke-width','2')
.attr('fill','none');
}
Note that this won't work for several lines -- to do that, change .data([data]) to .data(data) and use a nested array, e.g. [[{x:0,y:0},...], [{x:1,y:1},...], ...].
In my line chart, initially all data needed for plot line. There is two button Make and sell, If I click on any one of the button, data related to that button should be plotted for line with transition effect as shown in this link . I have tried to make this work, but I can't. I tried to make relation between button and line chart as given below(code), its not working. I have hard coded buttonId in my code with sell to have data related to sell, if I change it to Make I will get data related to Make but I need to hard coded here. what I want is when a Make button clicked data related to Make should come with transition as above link shown. My code is
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="d3.v3.min.js"></script>
<script type="text/javascript" src="d3.chart.min.js"></script>
</head>
<body>
<div id="vis"></div>
<button id="Make">Make</button>
<button id="sell">sell</button>
<script type="text/javascript">
d3.chart("linechart", {
initialize: function() {
// create a base scale we will use later.
var chart = this;
chart.w = chart.base.attr('width') || 200;
chart.h = chart.base.attr('height') || 150;
chart.w = +chart.w;
chart.h = +chart.h;
buttonId = 'sell'
chart.x = d3.scale.linear()
.range([0, chart.w]);
chart.y = d3.scale.linear()
.range([chart.h,0]);
chart.base.classed('line', true);
this.areas = {};
chart.areas.lines = chart.base.append('g')
.classed('lines', true)
.attr('width', chart.w)
.attr('height', chart.h)
chart.line = d3.svg.line()
.x(function(d) { return chart.x(d.x);})
.y(function(d) { return chart.y(d.y);});
this.layer("lines", chart.areas.lines, {
dataBind: function(data) {
// update the domain of the xScale since it depends on the data
chart.y.domain([d3.min(data,function(d){return d.y}),d3.max(data,function(d){return d.y})])
var mydata=new Array();
if(buttonId)
{
var j=0;
for(var i in data)
{
if(data[i].custody === buttonId){mydata[j] = data[i]; j++;};
}
chart.x.domain(d3.extent(mydata, function(d) { return d.x; }));
}
else
{
chart.x.domain(d3.extent(data, function(d) { return d.x; }));
}
// return a data bound selection for the passed in data.
return this.selectAll("path").data([data]);
},
insert: function() {
return this.append("path")
.datum(data)
.attr("d", chart.line)
.attr('stroke','#1ABC9C')
.attr('stroke-width','2')
.attr('fill','none');
}
});
},
// configures the width of the chart.
// when called without arguments, returns the
// current width.
width: function(newWidth) {
if (arguments.length === 0) {
return this.w;
}
this.w = newWidth;
return this;
},
// configures the height of the chart.
// when called without arguments, returns the
// current height.
height: function(newHeight) {
if (arguments.length === 0) {
return this.h;
}
this.h = newHeight;
return this;
},
});
var data = [
{x: 0,y:190, custody: "Make"},
{x: 1,y:10, custody: "Make"},{x: 2,y:40, custody: "Make"},{x: 3,y:90, custody: "Make"},
{x: 4,y:30, custody: "sell"},{x: 5,y:20, custody: "sell"},{x: 6,y:10, custody: "sell"},
{x: 7,y:40, custody: "sell"}
];
var chart1 = d3.select("#vis")
.append("svg")
.chart("linechart")
.width(720)
.height(320)
chart1.draw(data);
</script>
</body>
</html>
Get d3.chart.min.js from d3.chart.min.js
and get d3.v3.min.js from d3.v3.min.js
I have been trying to add a popup displaying the data type shown in each layer of my stacked area chart. Here is what I have done so far:
Basically when I mouse over either layer of the stacked area chart, im getting the same type displayed in the popup, I can see why this is, but I have no idea how to make it state one value when mouse is over that layer, and the other value when its over the second layer.
Please could anyone help?
/* y-axis scale is the largest data item scaled to height of the panel */
yScale = pv.Scale.linear(0, pv.max(data, function (d) {
var totalValue = 0;
for (i = 0; i < stackLayers.length; i++) {
totalValue = totalValue + d[stackLayers[i]];
}
return totalValue;
})).range(0, h),
colors = pv.Colors.category20().range();
/* render the area's using the predefined protovis stack layout */
vis.add(pv.Layout.Stack)
.layers(stackLayers)
.values(data)
.x(function (d) {
return xScale(d.x);
})
.y(function (d, p) {
return yScale(d[p]);
})
.layer.add(pv.Area)
.interpolate("cardinal")
.tension(0.97)
.fillStyle(function (d) {
return colors[this.parent.index];
})
.text(function (d) {
if(stackLayers[i]== "HAL")
{return "Dave";}
else
{return "Sorry";}
/* return (stackLayers[i]== "Hal" ? "Dave" : "Sorry");*/
})
.event("mouseover", pv.Behavior.tipsy({gravity:"s", fade:true}))
.anchor("top").add(pv.Line)
.strokeStyle(function (d) {
return colors[this.parent.index].darker();
})
.interpolate("cardinal")
.tension(0.97)
.lineWidth(2);
/* Y-axis rule. */
vis.add(pv.Rule)
.data(yScale.ticks(5))
.left(0)
.width(w)
.bottom(yScale)
.strokeStyle(function (d) {
return d ? "rgba(128,128,128,.2)" : "#000";
})
.anchor("left").add(pv.Label)
.text(yScale.tickFormat);