CSV not returning rows - javascript

been stuck on this all morning. I've been following this example
I've been trying to get this to work in React but sadly no luck. I was able to read the csv in before and console log the data so I know it's finding the right file. What am I missing here?
class Linegraph extends Component {
componentDidMount() {
var parseDate = d3.timeParse("%m/%d/%Y");
var margin = { left: 50, right: 20, top: 20, bottom: 50 };
var width = 960 - margin.left - margin.right;
var height = 500 - margin.top - margin.bottom;
var max = 0;
var xNudge = 50;
var yNudge = 20;
var minDate = new Date();
var maxDate = new Date();
d3.csv("MOCK_DATA.csv")
.row(function(d) {
return {
month: parseDate(d.month),
price: Number(d.price.trim().slice(1))
};
})
.get(function(error, rows) {
max = d3.max(rows, function(d) {
return d.price;
});
minDate = d3.min(rows, function(d) {
return d.month;
});
maxDate = d3.max(rows, function(d) {
return d.month;
});
.................

(note: this question is not a duplicate of the existing Q/A about the new d3.fetch module because it uses a row function, not covered on those Q/A)
Since you are using D3 v5, you have to change the XmlHttpRequest pattern of v4 to the new Promises pattern of v5 (see the documentation here).
So, your code should be:
d3.csv("MOCK_DATA.csv", function(d) {
return {
month: parseDate(d.month),
price: Number(d.price.trim().slice(1))
};
})
.then(function(rows) {
//etc...
});
Pay attention to the fact that the row function goes inside the d3.csv function, as the second argument.

Related

Using D3 for painting grid cells multiple times?

I have a tsv file with 100000+ lines and 3 columns: time(ascending, nanoseconds);object;color. In this file each object appears multiple times with different color and time. And i have a grid, where each cell represents one of the objects (about 500+) and their actual color.
Here is the Code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
svg {
width: 90%;
height: 400px;
}
</style>
</head>
<body>
<div id="vis">
<button id="play-button" onclick="runMain()" style="margin-left: 2em; margin-top:2em;">Play </button>
<label for="inputDelay">delay:</label>
<input type="text" id="inputDelay" placeholder="20" value="20">
</div>
<svg></svg>
</body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-timer.v1.min.js"></script>
<link type="text/css" href="style.css" rel="stylesheet" />
<script type="text/javascript">
let nodeColors2 = ["#33b5e5", "#cc0000", "#669900", "#b4e56b", "#ffbb33", "#f540ff", "#aa18ff", "#16ffa9", "gray"]
var playButton = d3.select("#play-button");
createSVG();
function createSVG(numParam) {
var svg = d3.select("svg");
var row = svg.selectAll(".row")
.data(createGrid(numParam))
.enter().append("g")
.attr("class", "row");
var column = row.selectAll(".square")
.data(function(d) {
return d;
})
.enter().append("rect")
.attr("class", "square")
.attr("id", function(d) {
return "rect" + d.id
})
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
})
.attr("width", function(d) {
return d.width;
})
.attr("height", function(d) {
return d.height;
})
.style("fill", "#fff")
.style("stroke", "#222");
}
/**prescan dataSet for further computation **/
function fileScanning() {
return new Promise(function(resolve, reject) {
let parameterList = [];
setTimeout(() => reject(new Error("setTimeoutError")), 1000);
d3.tsv('testFile.tsv', function(data) {
data.forEach(function(d) {
if (!parameterList.includes(d.param)) {
parameterList.push(d.param);
}
});
resolve(parameterList.sort((a, b) => a - b));
});
});
}
/** create grid: cells get a squareID for parameters **/
function createGrid(numParam = 600) {
var num_columns = 50;
var num_rows = Math.ceil(numParam / num_columns);
var data = new Array();
var xpos = 10; //starting xpos and ypos at 10 so the stroke will show when we make the grid below
var ypos = 10;
var width = 20;
var height = 20;
var squareID = 0;
// iterate for rows
for (var row = 0; row < num_rows; row++) {
data.push(new Array());
// iterate for cells/columns inside rows
for (var column = 0; column < num_columns; column++) {
data[row].push({
x: xpos,
y: ypos,
width: width,
height: height,
id: "" + squareID
});
// each cell gets an ID for further linking to parameters
squareID++;
// increment the x position. I.e. move it over by 50 (width variable)
xpos += width;
}
// reset the x position after a row is complete
xpos = 10;
// increment the y position for the next row. Move it down 50 (height variable)
ypos += height;
}
return data;
}
async function runMain() {
let delay = document.getElementById('inputDelay').value;
d3.select("svg").selectAll(".row").remove();
let parameterList = await fileScanning();
createSVG(parameterList.length);
/** run through dataSet and change nodecolors line for line**/
d3.tsv('testFile.tsv', function(data) {
for (let i = 0; i < data.length; i++) {
setTimeout(() => {
document.querySelector("#rect" + parameterList.indexOf(data[i].param)).setAttribute("style", "fill:" + nodeColors2[data[i].target] + "; stroke: black;");
}, i * delay);
}
});
}
</script>
</html>
The dataset looks like this where object=param and color=target:
time param target
5396736 0 0
21620736 307000 5
36134400 430000 7
38073600 369000 6
39064064 246000 4
39318784 123000 2
48853504 62000 1
58494720 1000 0
65408512 185000 3
87599616 431000 7
87736832 247000 4
90412544 308000 5
91149568 370000 6
96732416 63000 1
96962816 124000 2
116216064 2000 0
117147392 186000 3
i want to start the visualisation (like youtube: variable speed, slider, possible to play/pause, back and forth) and while time is running it should always check the time in the file if an object needs to be updated. so, e.g. beeing at 45 seconds and all objects should represent their latest color update until that time.
As a newbie in general but especially in D3 i am wondering if D3 is a good choice and if so how my approach should look like or if i shouldn't use D3 with these requirements at all? I didn't figure out to fully understand update,enter and exit, yet but it feels like it should be the answer. If so i have no clue how the approach for "rewinding" should look like, since it shouldn't delete an object but repaint it with its latest color before the chosen time.
Here is the latest working example on plunker with a very small dataset for better understanding. It is supposed to look like this (missing the whole slider functionality like in youtube). But it is not supposed to use setTimeout for the moments of painting. I want to use "real time" from the tsv file.
As best fit got these sources: slider, grid.
With slider i am having trouble to change the Date approach into my nanoseconds.
Do you have some help if D3 will work and how to approach all of this since i am a bit lost?
PS: First question for me: Any suggestion for a better title of this question? I am happy to edit my question to make it more comfortable for everyone.

D3 Animate One Path Series At a Time

Learning Javascript and D3.
Trying to create a graph where it draws each line in a series, one at a time or on a delayed interval.
What I've got so far (relevant code at the end of this post)
https://jsfiddle.net/jimdholland/5n6xrLk0/180/
My data is structures with each series as one row, with 12 columns. When you read it in, the object approximately looks like
mydata = [
{NumDays01: "0", NumDays02: "0", NumDays03: "0", NumDays04: "0", NumDays05: "0",Numdays06: 30}
1: {NumDays01: "0", NumDays02: "0", NumDays03: "0", NumDays04: "0",...
]
I can get it to create line, but it draws all the paths at once. Likewise, I've tried a loop, but that still draws them all at once. Also tried d3.timer and similar results. But I'm having a hard time understanding the timer callback stuff and creating those functions correctly.
I haven't found another example to really study other than a chart from FlowingData which has a lot of bells and whistles out of my league.
https://flowingdata.com/2019/02/01/how-many-kids-we-have-and-when-we-have-them/
Any help or tips would be appreciated.
var svg = d3.select("#chart").select("svg"),
margin = { top: 30, right: 20, bottom: 10, left: 40 },
width = parseInt(d3.select("#chart").style('width'), 10) - margin.left - margin.right;
d3.tsv("https://s3.us-east-2.amazonaws.com/jamesdhollandwebfiles/data/improvementTest.tsv", function(error, data) {
if (error) throw error;
console.log(data);
myData = data;
var t = d3.timer(pathMaker);
}); // #end d3.tsv()
function pathMaker() {
var peeps = myData[rowToRun];
var coords = [];
var lineToRemove = [];
for (var nameIter in dayList) {
coords.push({ x: x(nameIter), y: y(peeps[dayList[nameIter]])})
}
var improvementPath = g.append("path")
.datum(coords)
.attr("id", "path" + peeps.caseid)
.attr("d", lineMaker)
.attr("stroke", "#90c6e4")
.attr("fill", "none")
.attr("stroke-width", 2);
var total_length = improvementPath.node().getTotalLength();
var startPoint = pathStartPoint(improvementPath);
improvementPath = improvementPath
.attr("stroke-dasharray", total_length + " " + total_length)
.attr("stroke-dashoffset", total_length)
.transition() // Call Transition Method
.duration(4000) // Set Duration timing (ms)
.ease(d3.easeLinear) // Set Easing option
.attr("stroke-dashoffset", 0); // Set final value of dash-offset for transition
rowToRun += 1;
if (rowToRun == 5) {rowToRun = 0;}
}
//Get path start point for placing marker
function pathStartPoint(Mypath) {
var d = Mypath.attr("d"),
dsplitted = d.split(/M|L/)[1];
return dsplitted;
}
there are a few problems. I tried to configure your code as little as I could to make it work. If you need further explanations, please let me know
d3.tsv("https://s3.us-east-2.amazonaws.com/jamesdhollandwebfiles/data/improvementTest.tsv", function(error, data) {
if (error) throw error;
console.log(data);
myData = data;
pathMaker()
}); // #end d3.tsv()
function pathMaker() {
var peeps = myData[rowToRun];
var coords_data = [];
var lineToRemove = [];
for (let i = 0; i < myData.length; i++) {
var coords = [];
for (var nameIter in dayList) {
coords.push({ x: x(nameIter), y: y(myData[i][dayList[nameIter]])})
}
coords_data.push(coords)
}
console.log(coords_data)
var improvementPath = g.selectAll("path")
.data(coords_data)
.enter()
.append("path")
.attr("d", lineMaker)
.attr("stroke", "#90c6e4")
.attr("fill", "none")
.attr("stroke-width", 2);
improvementPath = improvementPath.each(function (d,i) {
var total_length = this.getTotalLength();
var startPoint = pathStartPoint(improvementPath);
const path = d3.select(this)
.attr("stroke-dasharray", total_length + " " + total_length)
.attr("stroke-dashoffset", total_length)
.transition() // Call Transition Method
.duration(4000) // Set Duration timing (ms)
.delay(i*4000)
.ease(d3.easeLinear) // Set Easing option
.attr("stroke-dashoffset", 0); // Set final value of dash-offset for transition
})
rowToRun += 1;
if (rowToRun == 5) {rowToRun = 0;}
}

ReactJS and D3JS: setState

I'm trying to visualize a stock price chart, using reactJS and D3JS(v5).
Fetching data from Alphavantage API
Framework = ReactJS (which, honestly, introduces an unnecessary layer of complexity)
D3JS for viz
Roughly three parts to this:
global function parseData(). Parses data from the fetch into a
format that d3 can read. Verified working, but included for
completeness.
componentDidMount() call within class App. Promise chain: has fetch call, then parseData(), then setState(), and finally
drawChart()
drawChart() local function within class App: contains all D3 logic. Will be separated out into its own component later. Works if I pass it data from a local json (commented out; some sample rows provided below), but not when I try to pass it
data from fetch.
Code so far: this is all in App.js, no child components:
import React, { Component } from 'react';
import './App.css';
import * as d3 from "d3";
//import testTimeData from "./data/testTimeData"
function parseData(myInput) {
// processes alpha vantage data into a format for viz
// output an array of objects,
// where each object is {"Date":"yyyy-mm-dd", "a":<float>}
let newArray = []
for (var key in myInput) {
if (myInput.hasOwnProperty(key)) {
const newRow = Object.assign({"newDate": new Date(key)}, {"Date": key}, myInput[key])
newArray.push(newRow)
}
}
//console.log(newArray)
// 2. Generate plotData for d3js
let newArray2 = []
for (var i = 0; i < newArray.length; i++) {
let newRow = Object.assign({"Date": newArray[i]["Date"]}, {"a":parseFloat(newArray[i]["4. close"])})
newArray2.unshift(newRow)
}
return newArray2
}
class App extends Component {
constructor() {
super()
this.state = {
ticker:"",
plotData:[]
}
this.drawChart = this.drawChart.bind(this)
}
// setState() in componentDidMount()
// fetch monthly data
// hardcode ticker = VTSAX for the moment
// and call this.drawChart()
componentDidMount() {
const ticker = "VTSAX"
const api_key = "EEKL6B77HNZE6EB4"
fetch("https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol="+ticker+"&apikey="+api_key)
.then(console.log("fetching..."))
.then(response => response.json())
.then(data => parseData(data["Monthly Time Series"]))
.then(data => console.log(data))
.then(data =>{this.setState({plotData:data, ticker:ticker})})
.then(this.drawChart());
}
drawChart() {
const stockPlotData = this.state.plotData
console.log("stockPlotData.length=", stockPlotData.length)
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 300)
var margin = {left:50, right:30, top:30, bottom: 30}
var width = svg.attr("width") - margin.left - margin.right;
var height = svg.attr("height") - margin.bottom - margin.top;
var x = d3.scaleTime().rangeRound([0, width]);
var y = d3.scaleLinear().rangeRound([height, 0]);
var parseTime = d3.timeParse("%Y-%m-%d");
x.domain(d3.extent(stockPlotData, function(d) { return parseTime(d.date); }));
y.domain([0,
d3.max(stockPlotData, function(d) {
return d.a;
})]);
var multiline = function(category) {
var line = d3.line()
.x(function(d) { return x(parseTime(d.date)); })
.y(function(d) { return y(d[category]); });
return line;
}
var g = svg.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var categories = ['a'];
for (let i in categories) {
var lineFunction = multiline(categories[i]);
g.append("path")
.datum(stockPlotData)
.attr("class", "line")
.style("stroke", "blue")
//.style("stroke", color(i))
.style("fill", "None")
.attr("d", lineFunction);
}
// append X Axis
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickFormat(d3.timeFormat("%Y-%m-%d")));
// append Y Axis
g.append("g")
.call(d3.axisLeft(y));
}
render(){
return (<div>{this.state.ticker}</div>)
}
}
export default App;
Outputs from console.log() calls:
data => console.log(data) within the promise chain of componentDidMount() correctly displays the fetched data
But console.log("stockPlotData.length=", stockPlotData.length) call in the drawChart() function returns stockPlotData.length= 0. Did I call this.setState() wrongly?
The page renders "VTSAX" correctly in the render(){return(...)} call at the bottom, indicating that this.setState updated the ticker variable correctly.
Some test data rows:
[
{"Date":"2016-01-15", "a": 220 },
{"Date":"2016-01-16", "a": 250},
{"Date":"2016-01-17", "a": 130},
{"Date":"2016-01-18", "a": 180},
{"Date":"2016-01-19", "a": 200},
]
That's a lot of code to read, but based on a quick glance, you probably want to call this.drawChart() inside of a .then instead of after your promise chain, because it's gonna fire before your promises resolve.

D3 semantic zooming with Reusable Pattern

I'm trying to implement semantic zooming while using Mike Bostock's Towards Reusable Charts pattern (where a chart is represented as a function). In my zoom handler, I'd like to use transform.rescaleX to update my scale and then simply call the function again.
It almost works but the rescaling seems to accumulate zoom transforms getting faster and faster. Here's my fiddle:
function chart() {
let aspectRatio = 10.33;
let margin = { top: 0, right: 0, bottom: 5, left: 0 };
let current = new Date();
let scaleBand = d3.scaleBand().padding(.2);
let scaleTime = d3.scaleTime().domain([d3.timeDay(current), d3.timeDay.ceil(current)]);
let axis = d3.axisBottom(scaleTime);
let daysThisMonth = d3.timeDay.count(d3.timeMonth(current), d3.timeMonth.ceil(current));
let clipTypes = [ClipType.Scheduled, ClipType.Alarm, ClipType.Motion];
let zoom = d3.zoom().scaleExtent([1 / daysThisMonth, 1440]);
let result = function(selection) {
selection.each(function(data) {
let selection = d3.select(this);
let outerWidth = this.getBoundingClientRect().width;
let outerHeight = outerWidth / aspectRatio;
let width = outerWidth - margin.left - margin.right;
let height = outerHeight - margin.top - margin.bottom;
scaleBand.domain(d3.range(data.length)).range([0, height * .8]);
scaleTime.range([0, width]);
zoom.on('zoom', _ => {
scaleTime = d3.event.transform.rescaleX(scaleTime);
selection.call(result);
});
let svg = selection.selectAll('svg').data([data]);
let svgEnter = svg.enter().append('svg').attr('viewBox', '0 0 ' + outerWidth + ' ' + outerHeight);//.attr('preserveAspectRatio', 'xMidYMin slice');
svg = svg.merge(svgEnter);
let defsEnter = svgEnter.append('defs');
let defs = svg.select('defs');
let gMainEnter = svgEnter.append('g').attr('id', 'main');
let gMain = svg.select('g#main').attr('transform', 'translate(' + margin.left + ' ' + margin.top + ')');
let gAxisEnter = gMainEnter.append('g').attr('id', 'axis');
let gAxis = gMain.select('g#axis').call(axis.scale(scaleTime));
let gCameraContainerEnter = gMainEnter.append('g').attr('id', 'camera-container');
let gCameraContainer = gMain.select('g#camera-container').attr('transform', 'translate(' + 0 + ' ' + height * .2 + ')').call(zoom);
let gCameraRowsEnter = gCameraContainerEnter.append('g').attr('id', 'camera-rows');
let gCameraRows = gCameraContainer.select('g#camera-rows');
let gCameras = gCameraRows.selectAll('g.camera').data(d => {
return d;
});
let gCamerasEnter = gCameras.enter().append('g').attr('class', 'camera');
gCameras = gCameras.merge(gCamerasEnter);
gCameras.exit().remove();
let rectClips = gCameras.selectAll('rect.clip').data(d => {
return d.clips.filter(clip => {
return clipTypes.indexOf(clip.type) !== -1;
});
});
let rectClipsEnter = rectClips.enter().append('rect').attr('class', 'clip').attr('height', _ => {
return scaleBand.bandwidth();
}).attr('y', (d, i, g) => {
return scaleBand(Array.prototype.indexOf.call(g[i].parentNode.parentNode.childNodes, g[i].parentNode)); //TODO: sloppy
}).style('fill', d => {
switch(d.type) {
case ClipType.Scheduled:
return '#0F0';
case ClipType.Alarm:
return '#FF0';
case ClipType.Motion:
return '#F00';
};
});
rectClips = rectClips.merge(rectClipsEnter).attr('width', d => {
return scaleTime(d.endTime) - scaleTime(d.startTime);
}).attr('x', d => {
return scaleTime(d.startTime);
});
rectClips.exit().remove();
let rectBehaviorEnter = gCameraContainerEnter.append('rect').attr('id', 'behavior').style('fill', '#000').style('opacity', 0);
let rectBehavior = gCameraContainer.select('rect#behavior').attr('width', width).attr('height', height * .8);//.call(zoom);
});
};
return result;
}
// data model
let ClipType = {
Scheduled: 0,
Alarm: 1,
Motion: 2
};
let data = [{
id: 1,
src: "assets/1.jpg",
name: "Camera 1",
server: 1
}, {
id: 2,
src: "assets/2.jpg",
name: "Camera 2",
server: 1
}, {
id: 3,
src: "assets/1.jpg",
name: "Camera 3",
server: 2
}, {
id: 4,
src: "assets/1.jpg",
name: "Camera 4",
server: 2
}].map((_ => {
let current = new Date();
let randomClips = d3.randomUniform(24);
let randomTimeSkew = d3.randomUniform(-30, 30);
let randomType = d3.randomUniform(3);
return camera => {
camera.clips = d3.timeHour.every(Math.ceil(24 / randomClips())).range(d3.timeDay.offset(current, -30), d3.timeDay(d3.timeDay.offset(current, 1))).map((d, indexEndTime, g) => {
return {
startTime: indexEndTime === 0 ? d : d3.timeMinute.offset(d, randomTimeSkew()),
endTime: indexEndTime === g.length - 1 ? d3.timeDay(d3.timeDay.offset(current, 1)) : null,
type: Math.floor(randomType())
};
}).map((d, indexStartTime, g) => {
if(d.endTime === null)
d.endTime = g[indexStartTime + 1].startTime;
return d;
});
return camera;
};
})());
let myChart = chart();
let selection = d3.select('div#container');
selection.datum(data).call(myChart);
<div id="container"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
Edit: The zoom handler below works fine, but I'd like a more general solution:
let newScaleTime = d3.event.transform.rescaleX(scaleTime);
d3.select('g#axis').call(axis.scale(newScaleTime));
d3.selectAll('rect.clip').attr('width', d => {
return newScaleTime(d.endTime) - newScaleTime(d.startTime);
}).attr('x', d => {
return newScaleTime(d.startTime);
});
The short answer is you need to implement a reference scale to indicate what the scale's base state is when unmanipulated by the zoom. Otherwise you will run into the problem you describe: "It almost works but the rescaling seems to accumulate zoom transforms getting faster and faster. "
To see why a reference scale is needed, zoom in on the graph and out (once each) without moving the mouse. When you zoom in, the axis changes. When you zoom out the axis does not. Note the scale factor on the intial zoom in and the first time you zoom out: 1.6471820345351462 on the zoom in, 1 on the zoom out. The number represents how much the to magnify/minify whatever it is we are zooming in on. On the initial zoom in we magnify by a factor of ~1.65. On the preceding zoom out we minify by a factor of 1, ie: not at all. If on the other hand you zoom out first, you minify by a factor of about 0.6 and then if you were to zoom in you magnify by a factor of 1. I've built a stripped down of your example to show this:
function chart() {
let zoom = d3.zoom().scaleExtent([0.25,20]);
let scale = d3.scaleLinear().domain([0,1000]).range([0,550]);
let axis = d3.axisBottom;
let result = function(selection) {
selection.each(function() {
let selection = d3.select(this);
selection.call(axis(scale));
selection.call(zoom);
zoom.on('zoom', function() {
scale = d3.event.transform.rescaleX(scale);
console.log(d3.event.transform.k);
selection.call(result);
});
})
}
return result;
}
d3.select("svg").call(chart());
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="550" height="200"></svg>
The scale should be relative to the initial zoom factor, usually 1. In otherwords, the zoom is cumulative, it records magnification/minification as a factor of the initial scale, not the last step (otherwise transform k values would only be one of three values: one value for zooming out, another for zooming in and one for remaining the same and all relative to the current scale). This is why rescaling the initial scale doesn't work - you lose the reference point to the initial scale that the zoom is referencing.
From the docs, if you redefine a scale with d3.event.transform.rescaleX, we get a scale that reflects the zoom's (cumulative) transformation:
[the rescaleX] method does not modify the input scale x; x thus
represents the untransformed scale, while the returned scale
represents its transformed view. (docs)
Building on this, if we zoom in twice in a row, the first time we zoom in we see the transform.k value is ~1.6x on the first time, the second time it is ~2.7x. But, since we rescale the scale, we apply a zoom of 2.7x on a scale that has already been zoomed in 1.6x, giving us a scale factor of ~4.5x rather than 2.7x. To make matters worse, if we zoom in twice and then out once, the zoom (out) event gives us a scale value that is still greater than 1 (~1.6 on first zoom in, ~2.7 on second, ~1.6 on zoom out), hence we are still zooming in despite scrolling out:
function chart() {
let zoom = d3.zoom().scaleExtent([0.25,20]);
let scale = d3.scaleLinear().domain([0,1000]).range([0,550]);
let axis = d3.axisBottom;
let result = function(selection) {
selection.each(function() {
let selection = d3.select(this);
selection.call(axis(scale));
selection.call(zoom);
zoom.on('zoom', function() {
scale = d3.event.transform.rescaleX(scale);
var magnification = 1000/(scale.domain()[1] - scale.domain()[0]);
console.log("Actual magnification: "+magnification+"x");
console.log("Intended magnification: "+d3.event.transform.k+"x")
console.log("---");
selection.call(result);
});
})
}
return result;
}
d3.select("svg").call(chart());
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="550" height="200"></svg>
I haven't discussed the x offset portion of the zoom, but you can imagine that a similar problem occurs - the zoom is cumulative but you lose the initial reference point that those cumulative changes are in reference to.
The idiomatic solution is to use a reference scale and the zoom to create a working scale used for plotting rectangles/axes/etc. The working scale is initially the same as the reference scale (generally) and is set as so: workingScale = d3.event.transform.rescaleX(referenceScale) on each zoom.
function chart() {
let zoom = d3.zoom().scaleExtent([0.25,20]);
let workingScale = d3.scaleLinear().domain([0,1000]).range([0,550]);
let referenceScale = d3.scaleLinear().domain([0,1000]).range([0,550]);
let axis = d3.axisBottom;
let result = function(selection) {
selection.each(function() {
let selection = d3.select(this);
selection.call(axis(workingScale));
selection.call(zoom);
zoom.on('zoom', function() {
workingScale = d3.event.transform.rescaleX(referenceScale);
var magnification = 1000/(workingScale.domain()[1] - workingScale.domain()[0]);
console.log("Actual magnification: "+magnification+"x");
console.log("Intended magnification: "+d3.event.transform.k+"x")
console.log("---");
selection.call(result);
});
})
}
return result;
}
d3.select("svg").call(chart());
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<svg width="550" height="200"></svg>

Updating Nvd3 chart priodically

I'm working with Nvd3 charts from the examples from their official website. Now I want a line chart to update periodically based on data sent from server but I couldn't found any useful Example for this on internet.
I have created a function which re-draws the chart when new data is arrived but i want to append every new point to the existing chart (like we can do in highcharts) but i'm stuck.
Here is the code I'm using for Updating the chart.
var data = [{
"key" : "Long",
"values" : getData()
}];
var chart;
function redraw() {
nv.addGraph(function() {
var chart = nv.models.lineChart().margin({
left : 100
})
//Adjust chart margins to give the x-axis some breathing room.
.useInteractiveGuideline(true) //We want nice looking tooltips and a guideline!
.transitionDuration(350) //how fast do you want the lines to transition?
.showLegend(true) //Show the legend, allowing users to turn on/off line series.
.showYAxis(true) //Show the y-axis
.showXAxis(true);
//Show the x-axis
chart.xAxis.tickFormat(function(d) {
return d3.time.format('%x')(new Date(d))
});
chart.yAxis.tickFormat(d3.format(',.1%'));
d3.select('#chart svg').datum(data)
//.transition().duration(500)
.call(chart);
nv.utils.windowResize(chart.update);
return chart;
});
}
function getData() {
var arr = [];
var theDate = new Date(2012, 01, 01, 0, 0, 0, 0);
for (var x = 0; x < 30; x++) {
arr.push({
x : new Date(theDate.getTime()),
y : Math.random() * 100
});
theDate.setDate(theDate.getDate() + 1);
}
return arr;
}
setInterval(function() {
var long = data[0].values;
var next = new Date(long[long.length - 1].x);
next.setDate(next.getDate() + 1)
long.shift();
long.push({
x : next.getTime(),
y : Math.random() * 100
});
redraw();
}, 1500);

Categories