I am using Plottable.js to draw chart on my page.
I would like to resize the chart when the window is being resized.
I've tried to set the size via css (as well as svg width and height attribute) with no success.
Here is my attempt to set via svg attribute:
$('#svgSample').attr('width', w);
$('#svgSample').attr('height', h);
Here is my attempt to set via css:
$('#svgSample').css({
'width': w + 'px',
'height': h + 'px'
});
Any idea?
Thanks
I believe all you need to do is to call Plot.redraw() after setting svg attributes
here's an example:
window.onload = function() {
var data = [
{ x: 1, y: 1 },
{ x: 2, y: 1 },
{ x: 3, y: 2 },
{ x: 4, y: 3 },
{ x: 5, y: 5 },
{ x: 6, y: 8 }
];
var xScale = new Plottable.Scales.Linear();
var yScale = new Plottable.Scales.Linear();
var plot = new Plottable.Plots.Scatter()
.addDataset(new Plottable.Dataset(data))
.x(function(d) { return d.x; }, xScale)
.y(function(d) { return d.y; }, yScale);
plot.renderTo("#chart");
$('#chart').attr('width', 500);
$('#chart').attr('height', 400);
plot.redraw();
}
</style> <link rel="stylesheet" type="text/css" href="https://rawgithub.com/palantir/plottable/develop/plottable.css"> <style type="text/css">
body { background-color: #AAA; }
svg { background-color: #FFF; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="https://rawgithub.com/palantir/plottable/develop/plottable.js"></script>
<svg id="chart" width="100" height="100"></svg>
Using CSS you can overwrite the width, by using CSS "important" property
Ex: width: 100% !important; apply this to your div and try
Thanks to Zoe, I have modified my code to response to redraw on windows resize.
var data = [
{ x: 1, y: 1 },
{ x: 2, y: 1 },
{ x: 3, y: 2 },
{ x: 4, y: 3 },
{ x: 5, y: 5 },
{ x: 6, y: 8 }
];
var xScale = new Plottable.Scales.Linear();
var yScale = new Plottable.Scales.Linear();
var xAxis = new Plottable.Axes.Category(xScale, "bottom");
var yAxis = new Plottable.Axes.Numeric(yScale, "left");
var plot = new Plottable.Plots.Bar()
.addDataset(new Plottable.Dataset(data))
.x(function(d) { return d.x; }, xScale)
.y(function(d) { return d.y; }, yScale);
var chart = new Plottable.Components.Table([
[yAxis, plotBar],
[null, xAxis]
]);
chart.renderTo("svg#svgSample");
window.addEventListener("resize", function () {
plot.redraw();
});
As you can see, I don't even need to worry about setting width and height in css. I think Plottable handle it when calling plot.redraw()!
Thanks Zoe!
Related
Given the following code which calls the update function which creates 4 nodes with a circle and text element nested in a g element, waits 500ms, then calls the function again with updated data:
var data1 = [
{ x: 10, y: 10, text: "A" },
{ x: 30, y: 30, text: "B" },
{ x: 50, y: 50, text: "C" },
{ x: 70, y: 70, text: "D" }
];
var data2 = [
{ x: 30, y: 10, text: "X" },
{ x: 50, y: 30, text: "Y" },
{ x: 70, y: 50, text: "Z" },
{ x: 90, y: 70, text: "W" }
];
var svg = d3.select("body").append("svg");
update(data1);
setTimeout(function() { update(data2); }, 500);
function update(data) {
var nodes = svg.selectAll(".node")
.data(data);
var nodesUpdate = nodes
.attr("class", "node update")
var nodesEnter = nodes.enter();
var node = nodesEnter.append("g")
.attr("class", "node enter")
node
.attr("transform", function(d) { return "translate("+d.x+","+d.y+")"; });
node.append("circle")
.attr("r", 10)
.style("opacity", 0.2);
node.append("text")
.text(function(d) { return d.text; });
}
With the code as it is the second call has no effect, because everything is set in the enter selection. I'm trying to make it so I can call update with new data, and change properties on both the enter and update selections, without duplicating code. I can achieve this for top-level elements (ie the g elements) using merge, by making this change:
node
.merge(nodesUpdate)
.attr("transform", function(d) { return "translate("+d.x+","+d.y+")"; });
Now the nodes update their position after 500ms. However, I haven't been able to figure out how to update the text element. If I do nodes.selectAll("text") I end up with nested data, which doesn't work.
I've scoured the following docs to try and figure this out:
https://bl.ocks.org/mbostock/3808218
https://github.com/d3/d3-selection
https://bost.ocks.org/mike/nest/
It should just be nodes.select when dealing with a subselection.
Here's a quick refactor with comments and clearer variable names:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script>
var data1 = [{
x: 10,
y: 10,
text: "A"
}, {
x: 30,
y: 30,
text: "B"
}, {
x: 50,
y: 50,
text: "C"
}, {
x: 70,
y: 70,
text: "D"
}];
var data2 = [{
x: 30,
y: 10,
text: "X"
}, {
x: 50,
y: 30,
text: "Y"
}, {
x: 70,
y: 50,
text: "Z"
}, {
x: 90,
y: 70,
text: "W"
}];
var svg = d3.select("body").append("svg");
update(data1);
setTimeout(function() {
update(data2);
}, 500);
function update(data) {
var nodesUpdate = svg.selectAll(".node")
.data(data); // UPDATE SELECTION
var nodesEnter = nodesUpdate.enter()
.append("g")
.attr("class", "node"); // ENTER THE Gs
nodesEnter.append("text"); // APPEND THE TEXT
nodesEnter.append("circle") // APPEND THE CIRCLE
.attr("r", 10)
.style("opacity", 0.2);
var nodesEnterUpdate = nodesEnter.merge(nodesUpdate); // UPDATE + ENTER
nodesEnterUpdate // MOVE POSITION
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
nodesEnterUpdate.select("text") // SUB-SELECT THE TEXT
.text(function(d) {
return d.text;
});
}
</script>
</body>
</html>
Without refactoring a lot of your code, the simplest solution is using a key in the data function, followed by an "exit" selection:
var nodes = svg.selectAll(".node")
.data(data, d=> d.text);
nodes.exit().remove();
Here is the demo:
var data1 = [{
x: 10,
y: 10,
text: "A"
}, {
x: 30,
y: 30,
text: "B"
}, {
x: 50,
y: 50,
text: "C"
}, {
x: 70,
y: 70,
text: "D"
}];
var data2 = [{
x: 30,
y: 10,
text: "X"
}, {
x: 50,
y: 30,
text: "Y"
}, {
x: 70,
y: 50,
text: "Z"
}, {
x: 90,
y: 70,
text: "W"
}];
var svg = d3.select("body").append("svg");
update(data1);
setTimeout(function() {
update(data2);
}, 500);
function update(data) {
var nodes = svg.selectAll(".node")
.data(data, d => d.text);
nodes.exit().remove();
var nodesUpdate = nodes
.attr("class", "node update")
var nodesEnter = nodes.enter();
var node = nodesEnter.append("g")
.attr("class", "node enter")
node
.merge(nodesUpdate)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("circle")
.attr("r", 10)
.style("opacity", 0.2);
node.append("text")
.text(function(d) {
return d.text;
});
}
<script src="https://d3js.org/d3.v4.min.js"></script>
This will create a different "enter" selection. If, on the other hand, you want to get the data bound to the "update" selection, you'll have to refactor your code.
This question builds on this question.
Using d3.js/dc.js, I have three (or more) charts. All have the same x-axis (a date series), so the nth datapoint on any chart will correspond exactly to the nth datapoint on the x-axis of the other charts.
When the user clicks on a dot point in one chart, I need to get the "y" data from the same point on the other 2+ charts and return an array or object or string with the chartID/y-datum from the other charts, something like this:
{"chart1":"30","chart2":"50","chart3":"10"}
Here is an example borrowed from Gerardo Furtado's answer to the above-referenced question. How would I modify Gerardo's example to return the datapoints from each chart?
var data = [{x:20, y:30},
{x:30, y:60},
{x:40, y:40},
{x:50, y:90},
{x:60, y:20},
{x:70, y:90},
{x:80, y:90},
{x:90, y:10}];
draw("#svg1");
draw("#svg2");
draw("#svg3");
function draw(selector){
var width = 250,
height = 250;
var svg = d3.select(selector)
.append("svg")
.attr("width", width)
.attr("height", height);
var xScale = d3.scaleLinear()
.domain([0, 100])
.range([30, width - 10]);
var yScale = d3.scaleLinear()
.domain([0,100])
.range([height - 30, 10]);
var circles = svg.selectAll("foo")
.data(data)
.enter()
.append("circle");
circles.attr("r", 10)
.attr("fill", "teal")
.attr("cx", d=>xScale(d.x))
.attr("cy", d=>yScale(d.y));
var xAxis = d3.axisBottom(xScale);
var yAxis = d3.axisLeft(yScale);
svg.append("g").attr("transform", "translate(0,220)")
.attr("class", "xAxis")
.call(xAxis);
svg.append("g")
.attr("transform", "translate(30,0)")
.attr("class", "yAxis")
.call(yAxis);
}
d3.selectAll("circle").on("mouseover", function(){
var thisDatum = d3.select(this).datum();
d3.selectAll("circle").filter(d=>d.x == thisDatum.x && d.y == thisDatum.y).attr("fill", "firebrick");
}).on("mouseout", function(){
d3.selectAll("circle").attr("fill", "teal")
})
#svg1 {
float: left;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="svg1"></div>
<div id="svg2"></div>
<div id="svg3"></div>
As you have several different data sets, I'll modify the answer I wrote in your previous question so we can have different y values.
First, let't put all data in an object. That way, we can access the different data sets later:
var dataObject = {
data1: [{
x: 10,
y: 30
}, ...
}],
data2: [{
x: 10,
y: 70
}, ...
}],
data3: [{
x: 10,
y: 10
}, ...
}]
};
Then, we call the draw function:
draw("#svg1", dataObject.data1);
draw("#svg2", dataObject.data2);
draw("#svg3", dataObject.data3);
So, to get what you want, in the mouseover...
d3.selectAll("circle").on("mouseover", function() {
var thisDatum = d3.select(this).datum();
findPoints(thisDatum);
})
We call this function:
function findPoints(datum) {
var myObject = {};
for (var i = 1; i < 4; i++) {
myObject["chart" + i] = dataObject["data" + i].filter(e => e.x === datum.x)[0].y;
}
console.log(myObject)//use return instead of console.log
}
Here is the demo:
var dataObject = {
data1: [{
x: 10,
y: 30
}, {
x: 20,
y: 60
}, {
x: 30,
y: 40
}, {
x: 40,
y: 90
}, {
x: 50,
y: 20
}, {
x: 60,
y: 90
}, {
x: 70,
y: 90
}, {
x: 80,
y: 10
}],
data2: [{
x: 10,
y: 70
}, {
x: 20,
y: 60
}, {
x: 30,
y: 80
}, {
x: 40,
y: 10
}, {
x: 50,
y: 10
}, {
x: 60,
y: 20
}, {
x: 70,
y: 10
}, {
x: 80,
y: 90
}],
data3: [{
x: 10,
y: 10
}, {
x: 20,
y: 20
}, {
x: 30,
y: 40
}, {
x: 40,
y: 90
}, {
x: 50,
y: 80
}, {
x: 60,
y: 70
}, {
x: 70,
y: 50
}, {
x: 80,
y: 50
}]
};
draw("#svg1", dataObject.data1);
draw("#svg2", dataObject.data2);
draw("#svg3", dataObject.data3);
function draw(selector, data) {
var width = 200,
height = 100;
var svg = d3.select(selector)
.append("svg")
.attr("width", width)
.attr("height", height);
var xScale = d3.scaleLinear()
.domain([0, 100])
.range([30, width - 10]);
var yScale = d3.scaleLinear()
.domain([0, 100])
.range([height - 30, 10]);
var circles = svg.selectAll("foo")
.data(data)
.enter()
.append("circle");
circles.attr("r", 5)
.attr("fill", "palegreen")
.attr("cx", d => xScale(d.x))
.attr("cy", d => yScale(d.y));
var xAxis = d3.axisBottom(xScale);
var yAxis = d3.axisLeft(yScale).ticks(2);
svg.append("g").attr("transform", "translate(0,70)")
.attr("class", "xAxis")
.call(xAxis);
svg.append("g")
.attr("transform", "translate(30,0)")
.attr("class", "yAxis")
.call(yAxis);
}
d3.selectAll("circle").on("mouseover", function() {
var thisDatum = d3.select(this).datum();
findPoints(thisDatum);
d3.selectAll("circle").filter(d => d.x == thisDatum.x).attr("fill", "firebrick");
}).on("mouseout", function() {
d3.selectAll("circle").attr("fill", "palegreen")
})
function findPoints(datum) {
var myObject = {};
for (var i = 1; i < 4; i++) {
myObject["chart" + i] = dataObject["data" + i].filter(e => e.x === datum.x)[0].y;
}
console.log(JSON.stringify(myObject))
}
#svg1, #svg2 {
float: left;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="svg1"></div>
<div id="svg2"></div>
<div id="svg3"></div>
I have the following running code in JavaScript's Plottable.js.
var data = [{ x: 1, y: 1 }, { x: 2, y: 3 }, { x: 3, y: 2 },
{ x: 4, y: 4 }, { x: 5, y: 3 }, { x: 6, y: 5 }];
var xScale = new Plottable.Scales.Linear();
var yScale = new Plottable.Scales.Linear();
var xAxis = new Plottable.Axes.Numeric(xScale, "bottom");
var yAxis = new Plottable.Axes.Numeric(yScale, "left");
var plot = new Plottable.Plots.Bar()
.addDataset(new Plottable.Dataset(data))
.x(function(d) { return d.x; }, xScale)
.y(function(d) { return d.y; }, yScale)
.animated(true);
new Plottable.Components.Table([
[null, null],
[yAxis, plot],
[null, xAxis]
]).renderTo("svg#example");
window.addEventListener("resize", function() {
plot.redraw();
});
<!doctype html>
<html>
<head>
</head>
<body>
<svg width="100%" height="100%" id="example"></svg>
<!-- Act on the thing -->
<link href="http://plottablejs.org/assets/css/application.min.css" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/plottable.js/1.12.0/plottable.css" rel="stylesheet"/>
<link href="http://plottablejs.org/assets/css/application.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/plottable.js/1.12.0/plottable.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/plottable.js/1.12.0/plottable.css" rel="stylesheet"/>
</body>
</html>
As stated in the image below, how can I modify my Javascript code so that it can include the horizontal line at y=2?
You can use Plottable.Components.GuideLineLayer:
var data = [{ x: 1, y: 1 }, { x: 2, y: 3 }, { x: 3, y: 2 },
{ x: 4, y: 4 }, { x: 5, y: 3 }, { x: 6, y: 5 }];
var xScale = new Plottable.Scales.Linear();
var yScale = new Plottable.Scales.Linear();
var xAxis = new Plottable.Axes.Numeric(xScale, "bottom");
var yAxis = new Plottable.Axes.Numeric(yScale, "left");
var plot = new Plottable.Plots.Bar()
.addDataset(new Plottable.Dataset(data))
.x(function(d) { return d.x; }, xScale)
.y(function(d) { return d.y; }, yScale)
.animated(true);
var guideline = new Plottable.Components.GuideLineLayer("horizontal")
.scale(yScale);
guideline.value(2);
var plots = new Plottable.Components.Group([guideline, plot]);
new Plottable.Components.Table([
[null, null],
[yAxis, plots],
[null, xAxis]
]).renderTo("svg#example");
window.addEventListener("resize", function() {
plot.redraw();
});
<!doctype html>
<html>
<head>
</head>
<body>
<svg width="100%" height="100%" id="example"></svg>
<!-- Act on the thing -->
<link href="http://plottablejs.org/assets/css/application.min.css" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/plottable.js/1.12.0/plottable.css" rel="stylesheet"/>
<link href="http://plottablejs.org/assets/css/application.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/plottable.js/1.12.0/plottable.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/plottable.js/1.12.0/plottable.css" rel="stylesheet"/>
</body>
</html>
I am trying to create stacked bar chart using plottable.js from the same dataset. Is it possible?
Here is my sample dataset
var data = [{ x: 0, y: 1, m: 10 },
{ x: 1, y: 1, m: 9 },
{ x: 2, y: 3, m: 5 },
{ x: 3, y: 2, m: 5 },
{ x: 4, y: 4, m: 8 },
{ x: 5, y: 3, m: 7 },
{ x: 6, y: 5, m: 5 }];
In the example posted on http://plottablejs.org/components/plots/stacked-bar/, it used two dataset.
var plot = new Plottable.Plots.StackedBar()
.addDataset(new Plottable.Dataset(primaryData).metadata(5))
.addDataset(new Plottable.Dataset(secondaryData).metadata(3))
.x(function(d) { return d.x; }, xScale)
.y(function(d) { return d.y; }, yScale)
.attr("fill", function(d, i, dataset) { return dataset.metadata(); }, colorScale)
.renderTo("svg#example");
My question is, since I am using the same dataset and I need to change the 'y' function into two distinct functions to something like this:
.y(function(d) { return d.y; }, yScale)
.y(function(d) { return d.m; }, yScale)
Is it possible? If yes, how?
Thanks...
You can create two different Plottable.Datasets based on the same data.
So start like this:
var ds1 = new Plottable.Dataset(data);
var ds2 = new Plottable.Dataset(data);
However, later you'll need to distinguish the two datasets to know which attribute to use, so you should add metadata
var ds1 = new Plottable.Dataset(data, {name: "ds1"});
var ds2 = new Plottable.Dataset(data, {name: "ds2"});
now later, you can reference the name of the dataset to know which value to return in the .y() call
plot.y(function(d, i, ds){
if(ds.metadata().name == "ds1"){
return d.y;
}
else{
return d.m;
}
}, yScale);
Trying to create a custom line chart in which there is only one simple line, with a gradient background - the background of every part of the line is determined according to the y-value at that point (changes in values are guaranteed to be mild).
I'm having trouble with the basic configuration. This is my code:
js:
// General definitions
var HEIGHT, MARGINS, WIDTH, formatDay, lineFunc, graph, graph_data, weekdays, x, xAxis, y, yAxis;
WIDTH = 360;
HEIGHT = 130;
MARGINS = {
top: 20,
right: 30,
bottom: 20,
left: 20
};
graph = d3.select("#graph");
// Define Axes
weekdays = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"];
formatDay = function(d) {
return weekdays[d % 6];
};
x = d3.scale.linear().range([MARGINS.left, WIDTH - MARGINS.right]).domain([
d3.min(graph_data, function(d) {
return d.x;
}), d3.max(graph_data, function(d) {
return d.x + 1;
})
]);
y = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([
d3.min(graph_data, function(d) {
return d.y;
}), d3.max(graph_data, function(d) {
return d.y;
})
]);
xAxis = d3.svg.axis().scale(x).orient("bottom").tickFormat(formatDay);
yAxis = d3.svg.axis().scale(y).tickSize(10).orient("left");
// Line Function
lineFunc = d3.svg.line().x(function(d) {
return x(d.x);
}).y(function(d) {
return y(d.y);
}).interpolate("basis");
// Define Line Gradient
graph.append("linearGradient").attr("id", "line-gradient").attr("gradientUnits", "userSpaceOnUse").attr("x1", 0).attr("y1", y(0)).attr("x2", 0).attr("y2", y(200)).selectAll("stop").data([
{
offset: "0%",
color: "#F0A794"
}, {
offset: "20%",
color: "#F0A794"
}, {
offset: "20%",
color: "#E6A36A"
}, {
offset: "40%",
color: "#E6A36A"
}, {
offset: "40%",
color: "#CE9BD2"
}, {
offset: "62%",
color: "#CE9BD2"
}, {
offset: "62%",
color: "#AA96EE"
}, {
offset: "82%",
color: "#AA96EE"
}, {
offset: "82%",
color: "#689BE7"
}, {
offset: "90%",
color: "#689BE7"
}, {
offset: "90%",
color: "1AA1DF"
}, {
offset: "100%",
color: "1AA1DF"
}
]).enter().append("stop").attr("offset", function(d) {
return d.offset;
}).attr("stop-color", function(d) {
return d.color;
});
// Draw Line
graph.append("svg:path").attr("d", lineFunc(graph_data));
// Draw Axes
graph.append("svg:g").attr("class", "x axis").attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")").call(xAxis);
graph.append("svg:g").attr("class", "y axis").attr("transform", "translate(" + MARGINS.left + ",0)").call(yAxis);
style
#line-gradient {
fill: none;
stroke: url(#line-gradient);
stroke-width: 7px;
stroke-linejoin: "round";
}
Sample data
graph_data = [{
x: 1,
y: 22
}, {
x: 2,
y: 20
}, {
x: 3,
y: 10
}, {
x: 4,
y: 40
}, {
x: 5,
y: 5
}, {
x: 6,
y: 30
}, {
x: 7,
y: 60
}]
What i'm getting looks like this:
Can any of you D3.js experts tell me what I'm doing wrong, and what needs to change in order for my line to be a line rather than an area, having the line background gradient explained above, and round edges?
Many thanks in advance!
Here's a fiddle: http://jsfiddle.net/henbox/gu4y7fk8/
You should give the path a class name, like this:
graph.append("svg:path")
.attr("class","chartpath")
.attr("d", lineFunc(graph_data));
And then the CSS styling you have should be on that path element rather than the lineargradient element
.chartpath { /*note: not #line-gradient*/
fill: none;
stroke: url(#line-gradient);
stroke-width: 7px;
stroke-linejoin: "round";
}
I also fixed up a couple of other things:
Missing # on a couple of the color codes, so changed (color: "1AA1DF" to color: "#1AA1DF"
I changed the max y value for the gradient from 200 to 60, so that the changing color gradient of the line is more visible in the example (.attr("y2", y(200)) to .attr("y2", y(60)))