Resizing D3.js and TechanJS chart - javascript

I'm using D3.js and TechanJS to construct a market depth chart for several assets and am having issues getting the chart to resize when window.onresize fires. I can neither get the axes nor the plots to resize whatsoever, although they do correctly size on initial drawing.
The problem I'm having is that the example code I have for resizing plots uses built-in TechanJS plots, which do not include an option for line plots. The closest I can find is volume, which isn't exactly correct... So instead I'm plotting the bids and asks as lines and do not know how to a) define them so as to me accessible by variable name and/or b) updated using .call
Here's how I'm plotting the axis to be resized and the bids and asks:
var xAxis = d3.axisBottom(x).scale(d3.scaleLinear().domain(xPrice).range([0, dim.width-dim.margin.left-dim.margin.right]));
...
svg.append("g")
.attr("class", "x axis bottom");
...
svg.append("path")
.datum(bids)
.attr("class", "line")
.attr("class", "bids")
.attr("id", "bids")
.attr("d", line);
svg.append("path")
.datum(asks)
.attr("class", "line")
.attr("class", "asks")
.attr("id", "asks")
.attr("d", line);
And here's the resize function(s):
depthchart.resize = function(selection) {
selection.call(resize).call(draw);
};
function resize(selection) {
dim.width = selection.node().clientWidth;
dim.height = selection.node().clientHeight;
dim.plot.width = dim.width - dim.margin.left - dim.margin.right;
dim.plot.height = dim.height - dim.margin.top - dim.margin.bottom;
var xRange = [0, dim.plot.width],
yRange = [dim.plot.height, 0],
yTicks = Math.min(30, Math.round(dim.height/15)),
xTicks = Math.min(20, Math.round(dim.width/100));
x.range(xRange);
xAxis.ticks(xTicks);
y.range(yRange);
yAxis.ticks(yTicks);
priceAnnotation.translate([0, yRange[0]]);
depthCrosshair.verticalWireRange([0, dim.plot.height]);
selection.select("svg")
.attr("width", dim.width);
selection.selectAll("defs #depthClip > rect")
.attr("width", dim.plot.width)
.attr("height", dim.plot.height);
selection.select("g.x.axis")
.attr("transform", "translate(0, " + dim.plot.height + ")");
selection.selectAll("defs .plotClip > rect")
.attr("width", dim.plot.width)
.attr("height", dim.plot.height);
}
Any help on this would be greatly appreciated, thanks for reading!

Related

How do I draw gridlines in d3.js with zoom and pan

I have been able to make a scatter plot with zoom and pan functionality where the axes scale properly and everything works well. Now I am trying to figure out how to add gridlines, but running into some issues. I have started with only adding x-axis gridlines to figure things out. I have attached a fiddle with a working example to build from.
I commented out the initial gridlines when the graph is generated, because they would remain after zooming causing clutter, and I will add them back later when I get things working. When zooming the gridlines appear to be drawn correctly, but they do not match up with the x-axis labels, and the x-axis labels disappear after zooming or panning.
If you comment out line 163 and uncomment line 164 you can see the basic graph without any gridlines. Clicking the plot button will always generate a new graph. I have left behind some commented out code of different things that I have tried from searching through stackoverflow.
Example is using d3.js - 5.9.2
JSFiddle: https://jsfiddle.net/eysLvqkh/11/
HTML:
<div id="reg_plot"></div>
<button id="b" class="myButton">plot</button>
Javascript:
var theButton = document.getElementById("b");
theButton.onclick = createSvg;
function createSvg() {
// clear old chart when 'plot' is clicked
document.getElementById('reg_plot').innerHTML = ""
// dimensions
var margin = {top: 20, right: 20, bottom: 30, left: 55},
svg_dx = 1200,
svg_dy =600,
chart_dx = svg_dx - margin.right - margin.left,
chart_dy = svg_dy - margin.top - margin.bottom;
// data
var y = d3.randomNormal(400, 100);
var x_jitter = d3.randomUniform(-100, 1400);
var d = d3.range(1000)
.map(function() {
return [x_jitter(), y()];
});
// fill
var colorScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[1]; }))
.range([0, 1]);
// y position
var yScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[1]; }))
.range([chart_dy, margin.top]);
// x position
var xScale = d3.scaleLinear()
.domain(d3.extent(d, function(d) { return d[0]; }))
.range([margin.right, chart_dx]);
// y-axis
var yAxis = d3.axisLeft(yScale);
// x-axis
var xAxis = d3.axisBottom(xScale);
// append svg to div element 'reg_plot' and set zoom to our function named 'zoom'
var svg = d3.select("#reg_plot")
.append("svg")
.attr("width", svg_dx)
.attr("height", svg_dy);
svg.call(d3.zoom().on("zoom", zoom));
// clip path - sets boundaries so points will not show outside of the axes when zooming/panning
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("id", "clip-rect")
.attr("x", "0")
.attr("y", "0")
.attr('width', chart_dx)
.attr('height', chart_dy);
// plot data
var circles = svg.append("g")
.attr("id", "circles")
.attr("transform", "translate(75, 0)")
.attr("clip-path", "url(#clip)")
.selectAll("circle")
.data(d)
.enter()
.append("circle")
.attr("r", 4)
.attr("cx", function(d) { return xScale(d[0]); })
.attr("cy", function(d) { return yScale(d[1]); })
.style("fill", function(d) {
var norm_color = colorScale(d[1]);
return d3.interpolateInferno(norm_color)
});
// add y-axis
var y_axis = svg.append("g")
.attr("id", "y_axis")
.attr("transform", "translate(75,0)")
.call(yAxis).style("font-size", "10px")
// add x-axis
var x_axis = svg.append("g")
.attr("id", "x_axis")
.attr("transform", `translate(${margin.left}, ${svg_dy - margin.bottom - margin.top})`)
.call(xAxis).style("font-size", "10px")
// add x and y grid lines
x_axis.call(xAxis.scale(xScale).ticks(20).tickSize(-chart_dy));
y_axis.call(yAxis.scale(yScale).ticks(20).tickSize(-chart_dx));
function zoom(e) {
// re-scale y axis during zoom
y_axis.transition()
.duration(50)
.call(yAxis.scale(d3.event.transform.rescaleY(yScale)));
// re-scale x axis during zoom
x_axis.transition()
.duration(50)
.call(xAxis.scale(d3.event.transform.rescaleX(xScale)));
// re-draw circles using new scales
var new_xScale = d3.event.transform.rescaleX(xScale);
var new_yScale = d3.event.transform.rescaleY(yScale);
// re-scale axes and gridlines
x_axis.call(xAxis.scale(new_xScale).ticks(20).tickSize(-chart_dy));
y_axis.call(yAxis.scale(new_yScale).ticks(20).tickSize(-chart_dx));
circles.data(d)
.attr('cx', function(d) {return new_xScale(d[0])})
.attr('cy', function(d) {return new_yScale(d[1])});
}
}
For anyone looking, I have solved this problem. I have updated the javascript in the original post, and updated the jsfiddle. If you are copying this code to your local machine where you are using d3.js 7.4.4 or higher then you need to change the lines that say d3.event.transform.... to just e.transform.

D3 Line Chart- Line Not Scaling properly (ClipPath, Zoom)

I am using D3 version 4 to parse through data and graph by date.
I have scaled everything to work nicely with zoom, however I cannot seem to keep the line from overflowing outside of the axes. I would like it to stay inside the chart and just cut off parts when the user zooms in.
Using a clip path, the lines/dots are cut off at the axes boundary, but when the user zooms in they overflow past but are still missing the original piece that was cut off (i.e. only half of a dot, just bigger and overflowing).
The whole project is viewable here: https://codepen.io/lahesty/pen/NzMVjj
Here are some important/relevant pieces:
// scale, set ranges
var x = d3.scaleLinear()
.range([0, width-100])
.domain(d3.extent(data, function(d) { return d.inspected_at; }));
var y = d3.scaleLinear()
.range( [height, 0])
.domain(d3.extent(data, function(d) { return d.temperature; }));
var zoom = d3.zoom()
.scaleExtent([1, 40])
.on("zoom", zoomed);
//// clip path
defs = svg
.append('g')
.attr('width', 100)
.attr('height', 0)
.append('defs')
defs.append('clipPath')
.attr('id', 'clipper')
.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', width)
.attr('height', height)
//append line
svg.append('g')
.append("path")
.attr('clip-path', 'url(#clipper)')
.attr("class", "line")
.attr("d", line(data))
.attr("stroke", "blue")
function zoomed() {
svg.selectAll(".line")
.attr("transform", d3.event.transform);
svg.selectAll("circle")
.attr("transform", d3.event.transform);
gX.call(xAxis.scale(d3.event.transform.rescaleX(x)))
gY.call(yAxis.scale(d3.event.transform.rescaleY(y)))}
I have updated your codepen here. I slightly changed how lines and circles are appended, and bound the clip path to a g group which houses the line/circles.
svg.append('g')
.attr('clip-path', 'url(#clipper)') .selectAll('path.line').data([data])
.enter().append("path")
.attr("class", "line")
.attr("d", line)
.attr("stroke", "blue");
// draw the plot data
svg.append('g').attr('clip-path', 'url(#clipper)') .selectAll("circle.dot")
.data(data)
.enter()
This way it clips the entire grouping.

D3.js -- How do I update dataset via Javascript?

I have a dataset with 11 different variables (csv file with 12 columns). I want to be able to select a certain column for my scatterplot, but I'm having some difficulties. Please bear with me, as JavaScript is not my strong suit (obviously). Here's what I attempted:
<div class="variables" id="fixedacidity" onclick="drawPlot('fixedacidity');">
<h1>fixed acidity</h1>
</div>
<div class="variables" id="volatileacidity" onclick="drawPlot('volatileacidity');">
<h1>volatile acidity</h1>
</div>
<div class="variables" id="citricacid" onclick="drawPlot('citricacid');">
<h1>citric acid</h1>
</div>
<div class="variables" id="residualsugar" onclick="drawPlot('residualsugar');">
<h1>residual sugar</h1>
</div>
etc ...
I made a simple menu that calls on the drawPlot function, but I'm having trouble trying to get the variable to pass on correctly.
Relevant d3/javascript:
function drawPlot(selectedVar){
$(".visarea").html("");
var wineVar = selectedVar;
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 860 - margin.left - margin.right,
height = 350 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0])
.domain([0,10]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left").ticks(10);
var chart1 = d3.select(".visarea").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv("red.csv", function(error, data) {
if (error) throw error;
data.forEach(function(d) {
d.wineVar = +d.wineVar;
d.quality = +d.quality;
});
x.domain(d3.extent(data, function(d) { return d.wineVar; })).nice();
y.domain([0,10]).nice();
chart1.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text(wineVar);
chart1.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Rated Quality")
chart1.selectAll(".red.dot")
.data(data)
.enter().append("circle")
.attr("class", "red dot")
.attr("r", 3)
.attr("cx", function(d) { return x(d.wineVar); })
.attr("cy", function(d) { return y(d.quality); })
.style("fill", "red");
});
}
Although the variable gets passed on to the function, d.wineVar, as expected, does not return the desired value, and thus the chart does not draw the correct values. Can anyone recommend a workaround for this? It seems so simple, yet I've spent hours failing trying to figure this out.
Sample of red.csv:
fixedacidity,volatileacidity,citricacid,residualsugar,chlorides,freesulfurdioxide,totalsulfurdioxide,density,pH,sulphates,alcohol,quality
7.4,0.7,0,1.9,0.076,11,34,0.9978,3.51,0.56,9.4,5
7.8,0.88,0,2.6,0.098,25,67,0.9968,3.2,0.68,9.8,5
7.8,0.76,0.04,2.3,0.092,15,54,0.997,3.26,0.65,9.8,5
Image of what I'm trying to accomplish. The first dataset, fixedacidity, gets drawn up fine. I'm having difficulties trying to get the menu to correctly show its respective dataset. "Rated Quality" will always be the data for the Y-axis.
You has wrong variable reference, here:
data.forEach(function(d) {
d.wineVar = +d.wineVar; // <---------Here
d.quality = +d.quality;
});
change by:
data.forEach(function(d) {
d.wineVar = +d[wineVar]; // <----------Here
d.quality = +d.quality;
});
There is the obvious issue pointed out by klaujesi about data extraction. But there are more issues with your code.
I would say you need to adapt your approach to the way d3.js works. Currently you will add a new svg on each call to the function, caused by this line in your code: d3.select(".visarea").append("svg")
I usually have some init code wrapped in one function, which creates the svg and sets ups everything static. Then there is an update function which will handle input changes to show different data, use different scales etc.
The nice thing about d3.js is that you can control very easily what's to happen with newly introduced data via .enter() and removed data via .exit().

x-axis zooming with d3 line charts

I'm just getting into using d3, and relatively novice in js still. I'm trying to set up a page of log file visualizations for monitoring some servers. Right now I'm focusing on getting a line chart of CPU utilization, where I can focus on specific time periods (So an x-zoom only). I am able to do a static charts easily, but when it comes to the zooming the examples are going over my head and I can't seem to make them work.
This is the static example I followed to get things up and running, and this is the zoom example I've been trying to follow.
My input csv is from a rolling set of log files (which will not be labled on the first row), each row looks like this:
datetime,server,cpu,memory,disk,network_in,network_out
So far what I've been able to get on the zoom looks like this:
var parseDate = d3.time.format("%Y-%m-%d %H:%M:%S").parse;
// define dimensions of graph
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(-height, 0)
.tickPadding(6);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(-width)
.tickPadding(6);
// Define how we will access the information needed from each row
var line = d3.svg.line()
.interpolate("step-after")
.x(function(d) { return x(d[0]); })
.y(function(d) { return y(d[2]); });
// Insert an svg element into the document for each chart
svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Declare zoom handler
var zoom = d3.behavior.zoom().on("zoom", draw);
// Open the log and extract the information
d3.text("log.csv", function(text) {
var data = d3.csv.parseRows(text).map(function(row) {
return row.map(function(value, index) {
if (index == 0) {
return parseDate(value);
}
else if (index > 1) {
return +value;
}
else {
return value;
}
});
});
// Set the global minimum and maximums
x.domain(d3.extent(data, function(d) { return d[0]; }));
y.domain(d3.extent(data, function(d) { return d[2]; }));
zoom.x(x);
// Finally, we have the data parsed, and the parameters of the charts set, so now we
// fill in the charts with the lines and the labels
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.attr("text-anchor", "end")
.text("Percent (%)");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
svg.append("text")
.attr("x", margin.left)
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.text('all');
svg.append("rect")
.attr("class", "pane")
.attr("width", width)
.attr("height", height)
.call(zoom);
svg.select("path.line").data([data]);
draw();
});
function draw() {
svg.select("g.x.axis").call(xAxis);
svg.select("g.y.axis").call(yAxis);
svg.select("path.line").attr("d", line);
}
What this gives me is a very sluggish chart that can be zoomed and panned, but it does not clip off the line at the ends of the chart. I've tried adding in the clipping elements described in the example, but that ends up fully erasing my line every time.
Thanks for any help or direction

Dynamically update chart data in D3

I'm working on a real-time visualization of incoming data. I use D3 for the visualization and started based on this example: http://bl.ocks.org/mbostock/3883195
This is the version I'm currently working on:
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 500 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
var yAxis = d3.svg.axis().scale(y).orient("left");
var area = d3.svg.area()
.x(function(d) { return x(d.x); })
.y0(height)
.y1(function(d) { return y(d.y); });
var svg = d3.select("div#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var data = [[1,1],[2,3],[3,2],[4,4]];
var dataCallback = function(d) {
d.x = +d[0];
d.y = +d[1];
};
data.forEach(dataCallback);
x.domain(d3.extent(data, function(d) { return d.x; }));
y.domain([0, d3.max(data, function(d) { return d.y; })]);
svg.append("path")
.datum(data)
.attr("class", "area")
.attr("d", area);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Number of Messages");
This produces the following graph:
Now, as it should update itself regularly, I wanted to dynamically update the data in the graph using the following code:
var n = svg.selectAll("path").data([5,20])
n.enter();
n.exit().remove();
However, that does not work. I'm new to D3 and still learning the basics. Ideally, the graph visualization shifts to the left and the new data is shown in the graph. But so far I can't even add new data to it. Could someone help me with that? I also searched for examples similar to this, but did not found anything which really helped me so far.
d3 is pretty good at keeping track of what data's been added and what's being removed - it does this with joins. Take a look at http://bl.ocks.org/mbostock/3808218
IF we use the data binding (instead of "datum")
svg.append("path")
.data([data])
.attr("class", "area")
.attr("d", area);
and then on update do something like:
// update the list of coordinates
data.splice(0,1);
data.push([5,20]);
// re-decorate the last, newly added item
dataCallback(data[data.length - 1]);
// You will also need to update your axes
x.domain(d3.extent(data, function(d) { return d.x; }));
y.domain([0, d3.max(data, function(d) { return d.y; })]);
// update the data association with the path and recompute the area
svg.selectAll("path").data([data])
.attr("d", area);
And you should get your area to update.
You can try it out here
With the way your data is organized using the joined data (instead of datum) may not make much difference. Note that here the (single) data element you are associating with the path is the full list of coordinates so to update the area you need to pass in a new full list of coordinates.
If you really want to minimized the amount you need to redraw you could break up the area and draw each line segment. Then essentially you fall back to the bar chart example but with not-flat bars and no spacing.

Categories

Resources