Related
I would like to change the size of my legend because after using a position inset with bottom-left my legends elements doesn't have the same space between element:
https://user-images.githubusercontent.com/9460795/58431609-d9e77400-80ae-11e9-97c6-9efbd89bd558.png
I already tried to change the bar width but nothing happen
legend: { position: 'inset', padding: 10, inset: { anchor: 'bottom-left', x: -5, y: -50 - (20 * (this.selectedData.length - 2)), step: 1 }, item: { onmouseover: (id) => this.mouseOverLegendItem(id), onmouseout: (id) => this.mouseOutLegendItem(id), onclick: (id) => this.onmouseClickLegendItem(id) } }, bar: { width: 30 },
How can i fix it please ?
I'm newbi on c3 sorry :(
C3 version:"^0.6.13",
D3 version: "^5.9.1"
Unfortunately this fixed width seems to be baked into the positioning code when the legend is of type 'inset'. If you want the legend to display horizontally with no extra spacing between the items you're going to have to set the legend position as 'bottom'.
There is if you do that a hack whereby you can move that bottom legend into the chart area to mimic an inset though, but it's fairly brittle - see https://jsfiddle.net/5odf8w0x/
var chart = c3.generate({
data: {
columns: [
['data1ytuytuytutyuu', 30, 200, 100, 400, 150, 250],
['data2', 50, 20, 10, 40, 15, 25],
['data3', 10, 20, 30, 40, 60, 70]
]
},
legend: {
position: 'bottom'
},
onrendered: function () {
console.log(this, this.config);
var x = +d3.select(this.config.bindto).select(".c3-legend-item text").attr("x");
x -= 70; // for axis and margin
d3.select(this.config.bindto).selectAll(".c3-legend-item").attr("transform", "translate(-"+x+",-50)");
}
});
im new to D3 and love it so far, I was playing around with multi foci force layout and got a part of what I want to do working fine, What I would like to know if its possible to reposition some of the node based on a time scale.
What I meant by that is currently what you see is 6(orange), 5(blue) and 3 (green) nodes in the fiddle I created, So I want them to position themselves by translating towards each other i.e on day two data could be 5(orange), 6(blue) and 4(green), that means one orange node moves to green cluster.
var data = [
{"id": 0, "name": "x", "r":5 },
{"id": 0, "name": "x", "r":5 },
{"id": 0, "name": "x", "r":5 },
{"id": 0, "name": "x", "r":5 },
{"id": 0, "name": "x", "r":5 },
{"id": 1, "name": "y", "r":5 },
{"id": 1, "name": "y", "r":5 },
{"id": 1, "name": "y", "r":5 },
{"id": 1, "name": "y", "r":5 },
{"id": 1, "name": "y", "r":5 },
{"id": 1, "name": "y", "r":5 },
{"id": 2, "name": "z", "r":5 },
{"id": 2, "name": "z", "r":5 },
{"id": 2, "name": "z", "r":5 },
];
var width = window.innerWidth,
height = 410;
var fill = d3.scale.category10();
var nodes = [], labels = [],
foci = [{x: 0, y: 10}, {x: 100, y: 210}, {x: 600, y: 210}];
var svg = d3.select("body").append("svg")
.attr("width", "100%")
.attr("height", height)
//.attr("domflag", '');
var force = d3.layout.force()
.nodes(nodes)
.links([])
.charge(-10)
//.chargeDistance(100)
.gravity(0.1)
.friction(0.8)
.size([width, height])
.on("tick", tick);
//var node = svg.selectAll("circle");
var node = svg.selectAll("g");
var counter = 0;
function tick(e) {
var k = .1 * e.alpha;
// Push nodes toward their designated focus.
nodes.forEach(function(o, i) {
o.y += (foci[o.id].y - o.y) * k;
o.x += (foci[o.id].x - o.x) * k;
});
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
var timer = setInterval(function(){
if (nodes.length > data.length-1) { clearInterval(timer); return;}
var item = data[counter];
nodes.push({id: item.id, r: item.r, name: item.name});
force.start();
node = node.data(nodes);
var n = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.style('cursor', 'pointer')
.on('mousedown', function() {
var sel = d3.select(this);
//``sel.moveToFront();
})
.call(force.drag);
n.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return fill(d.id); })
n.append("text")
.text(function(d){
return d.name;
})
.style("font-size", function(d) {
return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 16) + "px";
})
.attr("dy", ".35em")
counter++;
}, 100);
node {
stroke-width: 1.5px;
}
.node:hover circle {
fill: grey !important;
}
text {
font: 18px 'Open Sans', sans-serif;;
text-anchor: middle;
pointer-events: none;
fill: white;
}
circle {
fill: #ccc;
stroke: white ;
stroke-width: 2px;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.0.0/d3.min.js"></script>
<h2>D3.js Multi-Foci Force layout </h2>
I have created a fiddle here.
What I want to know is how to translate nodes to another cluster and format data for looping through time.
I'm using D3 V3 for development.
Following code is used to generate lines in D3:
var lineFn = d3.line()
.x((d) => this.base.xAxis.scale(d.x))
.y((d) => this.base.yAxes[0].scale(d.y));
// series is a collection of lines I want to plot
series = [
{
data: [{x: 10, y: 20}, {x: 20, y: 30}],
yAxis: 0, // this indicates which y-axis to use
color: red
},
...
];
_.forEach(series, (line) => {
this.base.chart.append("path")
.datum(line.data)
.attr("class", "line")
.attr("d", lineFn)
.style("stroke", line.color)
});
My chart uses dual y-axes using d3.axisLeft() and d3.axisRight().
Right now, I am hardcoding the value of which y-axis to use in the lineFn.
.y((d) => this.base.yAxes[0].scale(d.y)); // 0-left axis, 1-right axis
What I would like to do is pass that value when I call the line function, something like:
.attr("d", lineFn(line.yAxis))
Is there any way to achieve this?
Thanks.
The easiest way to achieve what you want is simply creating two different line generators.
However, since you asked (not verbatim) "is it possible to define the scale dynamically when calling the line generator?", the answer is: yes, it is possible. Let's see how to do it.
In this example, I'm using an object to store the different scales:
var scales = {
yScaleLeft: d3.scaleLinear()
.domain([0, 100])
.range([170, 30]),
yScaleRight: d3.scaleLinear()
.domain([0, 200])
.range([170, 30])
};
And, in the dataset, defining which scale and color should be used for each line, just as you did:
var data = [{
data: [{
x: 1,
y: 20
}, {
...
}, {
x: 8,
y: 50
}],
yAxis: "yScaleLeft",
color: "red"
}, {
data: [{
x: 3,
y: 120
}, {
...
}, {
x: 9,
y: 180
}],
yAxis: "yScaleRight",
color: "blue"
}];
Then, when calling the line generator, we set a variable (in this case, thisScale) to specify the scale:
var thisScale;
paths.attr("stroke", d => d.color)
.attr("d", d => {
thisScale = scales[d.yAxis]
return line(d.data);
})
.attr("fill", "none");
Here is the demo, the red line uses a scale going from 0 to 100, the blue line uses a scale going from 0 to 200:
var svg = d3.select("body").append("svg")
.attr("width", 500)
.attr("height", 200);
var thisScale;
var line = d3.line()
.x(d => xScale(d.x))
.y(d => thisScale(d.y))
.curve(d3.curveMonotoneX);
var data = [{
data: [{
x: 1,
y: 20
}, {
x: 2,
y: 30
}, {
x: 3,
y: 10
}, {
x: 4,
y: 60
}, {
x: 5,
y: 70
}, {
x: 6,
y: 80
}, {
x: 7,
y: 40
}, {
x: 8,
y: 50
}],
yAxis: "yScaleLeft",
color: "red"
}, {
data: [{
x: 3,
y: 120
}, {
x: 4,
y: 130
}, {
x: 5,
y: 10
}, {
x: 6,
y: 120
}, {
x: 7,
y: 40
}, {
x: 8,
y: 130
}, {
x: 9,
y: 180
}],
yAxis: "yScaleRight",
color: "blue"
}];
var scales = {
yScaleLeft: d3.scaleLinear()
.domain([0, 100])
.range([170, 30]),
yScaleRight: d3.scaleLinear()
.domain([0, 200])
.range([170, 30])
};
var xScale = d3.scalePoint()
.domain(d3.range(11))
.range([30, 470])
var paths = svg.selectAll("foo")
.data(data)
.enter()
.append("path");
paths.attr("stroke", d => d.color)
.attr("d", d => {
thisScale = scales[d.yAxis]
return line(d.data);
})
.attr("fill", "none");
var xAxis = d3.axisBottom(xScale);
var yAxisLeft = d3.axisLeft(scales.yScaleLeft);
var yAxisRight = d3.axisRight(scales.yScaleRight);
var gX = svg.append("g").attr("transform", "translate(0,170)").call(xAxis);
var gY = svg.append("g").attr("transform", "translate(30,0)").call(yAxisLeft);
var gY2 = svg.append("g").attr("transform", "translate(470,0)").call(yAxisRight);
<script src="https://d3js.org/d3.v4.min.js"></script>
And here the same solution, but using an array (instead of an object) to store the scales, as you asked in your question:
yAxis: 0//indicates the left axis
yAxis: 1//indicates the right axis
var svg = d3.select("body").append("svg")
.attr("width", 500)
.attr("height", 200);
var thisScale;
var line = d3.line()
.x(d => xScale(d.x))
.y(d => thisScale(d.y))
.curve(d3.curveMonotoneX);
var data = [{
data: [{
x: 1,
y: 20
}, {
x: 2,
y: 30
}, {
x: 3,
y: 10
}, {
x: 4,
y: 60
}, {
x: 5,
y: 70
}, {
x: 6,
y: 80
}, {
x: 7,
y: 40
}, {
x: 8,
y: 50
}],
yAxis: 0,
color: "red"
}, {
data: [{
x: 3,
y: 120
}, {
x: 4,
y: 130
}, {
x: 5,
y: 10
}, {
x: 6,
y: 120
}, {
x: 7,
y: 40
}, {
x: 8,
y: 130
}, {
x: 9,
y: 180
}],
yAxis: 1,
color: "blue"
}];
var scales = [d3.scaleLinear()
.domain([0, 100])
.range([170, 30]), d3.scaleLinear()
.domain([0, 200])
.range([170, 30])
];
var xScale = d3.scalePoint()
.domain(d3.range(11))
.range([30, 470])
var paths = svg.selectAll("foo")
.data(data)
.enter()
.append("path");
paths.attr("stroke", d => d.color)
.attr("d", d => {
thisScale = scales[d.yAxis]
return line(d.data);
})
.attr("fill", "none");
var xAxis = d3.axisBottom(xScale);
var yAxisLeft = d3.axisLeft(scales[0]);
var yAxisRight = d3.axisRight(scales[1]);
var gX = svg.append("g").attr("transform", "translate(0,170)").call(xAxis);
var gY = svg.append("g").attr("transform", "translate(30,0)").call(yAxisLeft);
var gY2 = svg.append("g").attr("transform", "translate(470,0)").call(yAxisRight);
<script src="https://d3js.org/d3.v4.min.js"></script>
i'm exploring c3.js, i have created an donut chart, which was very simple to do, next thing i wanted to do is on mouser-over i wanted to expand/zoom/popout that focused segment, this functionality we can see in d3pai., but i'm trying to achieve this effect purely using c3.js.
can some one please suggest me how to proceed and how to create such poping-up of segment effect.
var init = function() {
var chart = c3.generate({
data: {
x: 'x',
columns: [
['x', '2013-01-01', '2013-01-02', '2013-01-03', '2013-01-04', '2013-01-05', '2013-01-06'],
['Coin1', 30, 200, 100, 400, 150, 250],
['Coin2', 130, 100, 140, 200, 150, 50],
['Coni3', 50, 100, 130, 240, 200, 150],
['Coin4', 130, 100, 140, 200, 150, 50],
['Coin5', 130, 150, 200, 300, 200, 100]
],
type: 'donut',
onclick: function(e) {
//console.log(e);
// console.log(d3.select(this).attr("stroke-width","red"));
},
onmouseover: function(d, i) {
},
onmouseout: function(d, i) {
}
},
axis: {
x: {
type: 'timeseries',
tick: {
format: '%Y-%m-%d',
centered: true,
position: 'inner-right'
}
}
},
bindto: '#dash',
bar: {
width: {
ratio: 0.5 // this makes bar width 50% of length between ticks
}
},
pie: {
expand: true,
},
tooltip: {
grouped: false,
contents: function(data, defaultTitleFormat, defaultValueFormat, color) {
// console.log("Containt");
// console.log(data, defaultTitleFormat, defaultValueFormat, color);
return "<p style='border:1px solid red;'>" + data[0].value + "</p>";
}
}
});
};
inti();
p {
line-height: 1;
font-weight: bold;
padding: 5px 12px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 4px;
line-height: 15px;
font-size: 12px;
min-width: 91px;
}
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.10/c3.js"></script>
</head>
<body>
<div id="dash"></div>
</body>
</html>
In the c3 config object, you can define onmouseover and onmouseout callback functions. The DOM node corresponding to the events is passed in as the second argument, so you can use it in the logic.
You can use that to apply things such as transformations. So on mouseover, you could scale it up, and on mouseout, scale it down. This is just a nudge in the right direction. You can play with other transformations to get the effect you want.
onmouseover: function (d, i) {
// 'i' is the dom node.
d3.select(i).attr("transform", "scale(1.1)")
},
onmouseout: function (d, i) {
d3.select(i).attr("transform", "scale(1)")
}
http://jsfiddle.net/ggamir/eqkrr5j0/
If you want the transformation to persist until the next mouse event, then you can keep track of the last item hovered over, and only "de-transform" it on the next mouseover:
http://jsfiddle.net/ggamir/79qhy9hn/
// Somewhere outside before defining your c3 config object:
var currentSlice;
// Inside your c3 config object:
onmouseover: function (d, i) {
if(currentSlice !== void 0) {
currentSlice.attr("transform","scale(1)")
}
currentSlice = d3.select(i).attr("transform", "scale(1.1)");
}
I'm just getting started with D3.js and am having a problem with getting the bars horizontally lined up. Currently they come out pointing downwards.
var jsonRectangles = [
{ "x_axis": 10, "y_axis": 0, "height": 65, "width":20, "color": "green" },
{ "x_axis": 40, "y_axis": 0, "height": 80, "width":20, "color": "purple" },
{ "x_axis": 70, "y_axis": 0, "height": 100, "width":20, "color": "orange" },
{ "x_axis": 100, "y_axis": 0, "height": 50, "width":20, "color": "brown" },
{ "x_axis": 130, "y_axis": 0, "height": 66, "width":20, "color": "black" },
{ "x_axis": 160, "y_axis": 0, "height": 68, "width":20, "color": "red" }];
var svgContainer = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 100);
var rectangles = svgContainer.selectAll("rect")
.data(jsonRectangles)
.enter()
.append("rect");
var rectangleAttributes = rectangles
.attr("x", function (d) { return d.x_axis; })
.attr("y", function (d) { return d.y_axis; })
.attr("height", function(d) { return height - y(d.weight); })
.attr("width", function (d) { return d.width; })
.style("fill", function(d) { return d.color; });
The (0,0) coordinate in an SVG is in the top left corner, so your y coordinates are "reversed" in the sense that they are counted from the top. This means that you have to position your bars so that they start at y position that you want to show and extend to the axis. Your code should look something like this.
rectangles.attr("y", function (d) { return (heightOfGraph - y(d.height)); })
.attr("height", function(d) { return y(d.height); });
On a general note, you don't need to save rectangleAttributes in a variable -- it will be exactly the same as rectangles.
In D3, 0 on the y coordinate is at the top rather than the bottom. You need to fist move the bars down to where you want the y axis origin to be, then move the bars up by their height to position them correctly.
Here's a rough solution though that hopefully you'll be able to work with (see the comments for the bits that have changed):
var jsonRectangles = [
{ "x_axis": 10, "y_axis": 0, "height": 65, "width":20, "color" : "green" },
{ "x_axis": 40, "y_axis": 0, "height": 80, "width":20, "color" : "purple" },
{ "x_axis": 70, "y_axis": 0, "height": 100, "width":20, "color" : "orange" },
{ "x_axis": 100, "y_axis": 0, "height": 50, "width":20, "color" : "brown" },
{ "x_axis": 130, "y_axis": 0, "height": 66, "width":20, "color" : "black" },
{ "x_axis": 160, "y_axis": 0, "height": 68, "width":20, "color" : "red" }];
// height of the visualisation - used to translate the bars
var viz_height = 100;
var svgContainer = d3.select("body").append("svg")
.attr("width", 500)
// set using viz_height rather than a fixed number
.attr("height", viz_height);
var rectangles = svgContainer.selectAll("rect")
.data(jsonRectangles)
.enter()
.append("rect");
var rectangleAttributes = rectangles
.attr("x", function (d) { return d.x_axis; })
// move the bars to the bottom of the chart (using
// viz_height), then move them back up by the height of
// the bar which moves them into palce
.attr("y", function (d) { return viz_height - y(d.height); })
.attr("height", function(d) { return y(d.height); })
.attr("width", function (d) { return d.width; })
.style("fill", function(d) { return d.color; });