I made a scatterplot that changes dynamically based on the variable for the Y axis that is selected in a dropdown box (the x axis is fixed). However the Y axis isn’t scaling/changing based on which variable is selected in the dropdown box.
// select svg canvas
var m = [20, 20, 30, 20], // margins
w = 1100-m[1]-m[3], // width
h = 650-m[0]-m[2], // height
xcol = 0, // active x column
ycol = 1; // active y column
// create svg element, adjusted by margins
var svg = d3.select('#chart')
.append("g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
// add the tooltip area to the webpage
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// load data from csv file
d3.csv('nbaWins.csv', function(data) {
// get columns of csv, mark excluded columns
var columns = d3.keys( data[0] ),
excluded = ['Team'];
// get quantitative dimensions
var dimensions = _(columns)
.difference(excluded);
// extents for each dimension
var extents = _(dimensions)
.map(function(col) {
return [0, d3.max(data, function(d) { return parseFloat(d[col]) })]
});
// x & y variables
var x = d3.scale.linear().domain(extents[xcol]).range([0, w - 50]),
y = d3.scale.linear().domain(extents[ycol]).range([h, 0]),
xAxis = d3.svg.axis().scale(x).orient("bottom"),
yAxis = d3.svg.axis().scale(y).orient("left");
// color scale (change colors later)
var color = {
"ATL": '#ff9896',
"BOS": '#ff9896',
"BKN": '#ff9896',
"CHA": '#ff9896',
"CHI": '#ff9896',
"CLE": '#ff9896',
"DAL": '#ff9896',
"DEN": '#ff9896',
"DET": '#ff9896',
"GSW": '#ff9896',
"HOU": '#ff9896',
"IND": '#ff9896',
"LAC": '#ff9896',
"LAL": '#ff9896',
"MEM": '#ff9896',
"MIA": "#ff9896",
"MIL": '#ff9896',
"MIN": '#ff9896',
"NO" : '#ff9896',
"NYK": '#ff9896',
"OKC": '#ff9896',
"ORL": '#ff9896',
"PHI": '#ff9896',
"PHO": '#ff9896',
"POR": '#ff9896',
"SAC": '#ff9896',
"SAS": '#ff9896',
"TOR": '#ff9896',
"UTA": '#ff9896',
"WAS": '#ff9896'
};
// create x axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", w - 40)
.attr("y", -6)
.style("text-anchor", "end")
.text("Wins");
// create y axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// bind data to chart
svg.selectAll('circle')
.data(data)
.enter().append('circle')
.attr("fill", function(d) { return color[d.Team]; })
.attr("cx", function(d) { return x(d[dimensions[xcol]]); })
.attr("cy", function(d) { return y(d[dimensions[ycol]]); })
.attr("r", 5) //backup
.on("mouseover", function(d) {
tooltip.transition().duration(200).style("opacity", .9);
tooltip.html(d["Team"] + "<br/> (" + d[dimensions[xcol]]
+ ", " + d[dimensions[ycol]] + ")")
.style("left", d + "px")
.style("top", d + "px");
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
});
// change x axis data (may not need: x axis is static)
function xaxis(i) {
xcol = i;
x.domain(extents[i]);
svg.selectAll('circle')
.transition()
.ease('linear')
.duration(1000)
.attr("cx", function(d) { return x(d[dimensions[0]]); })
.attr("cy", function(d) { return y(d[dimensions[ycol]]); });
};
// change y axis data
function yaxis(i) {
ycol = i;
y.domain(extents[i]);
svg.selectAll('circle')
.transition()
.ease('linear')
.duration(1000)
.attr("cx", function(d) { return x(d[dimensions[0]]); })
.attr("cy", function(d) { return y(d[dimensions[i]]); });
};
// create dropdowns to change y axes
d3.select("#yaxis")
.selectAll("option")
.data(dimensions)
.enter().append("option")
.attr("value", function(d,i) { return i; })
.text(function(d) { return d; })
.each(function(d,i) {
if (i == ycol) d3.select(this).attr("selected", "yes");
});
d3.select("#yaxis")
.on("change", function() { yaxis(this.selectedIndex) });
window.data = data;
});
Here is the console error I get when I try to change the Y variable:
NotFoundError: DOM Exception 8: An attempt was made to reference a Node in a context where it does not exist.
Any ideas on how to fix this? Any help would be much appreciated.
You've set the domain of the y but haven't redrawn the axis with it:
// change y axis data
function yaxis(i) {
ycol = i;
y.domain(extents[i]);
// update y axis
svg.selectAll("g.y.axis")
.call(yAxis);
svg.selectAll('circle')
.transition()
.ease('linear')
.duration(1000)
.attr("cx", function(d) { return x(d[dimensions[0]]); })
.attr("cy", function(d) { return y(d[dimensions[i]]); });
};
Example here.
Related
I adapted a multi-line chart which has a legend and axis and displays correctly on the bl.ocks.org site (http://bl.ocks.org/Matthew-Weber/5645518). The legend reorganizes itself when you select a different type from the drop down field. On my adaptation when the legend reorganizes itself the items start to overlap each other when some types are selected. Also the axes draw on top of each other. The original code uses tipsy but I have not checked it.
// original author's code http://bl.ocks.org/Matthew-Weber/5645518;
//set the margins
var margin = {
top: 50,
right: 160,
bottom: 80,
left: 50
},
width = 900 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//set dek and head to be as wide as SVG
d3.select('#dek')
.style('width', width + 'px');
d3.select('#headline')
.style('width', width + 'px');
//write out your source text here
var sourcetext = "xxx";
// set the type of number here, n is a number with a comma, .2% will get you a percent, .2f will get you 2 decimal points
var NumbType = d3.format(",");
// color array
var bluescale4 = ["red", "blue", "green", "orange", "purple"];
//color function pulls from array of colors stored in color.js
var color = d3.scale.ordinal().range(bluescale4);
//defines a function to be used to append the title to the tooltip.
var maketip = function(d) {
var tip = '<p class="tip3">' + d.name + '<p class="tip1">' + NumbType(d.value) + '</p> <p class="tip3">' + formatDate(d.date) + '</p>';
return tip;
}
//define your year format here, first for the x scale, then if the date is displayed in tooltips
var parseDate = d3.time.format("%Y-%m-%d").parse;
var formatDate = d3.time.format("%b %d, '%y");
//create an SVG
var svg = d3.select("#graphic").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 + ")");
//make a rectangle so there is something to click on
svg.append("svg:rect")
.attr("width", width)
.attr("height", height)
.attr("class", "plot"); //#fff
// force data to update when menu is changed
var menu = d3.select("#menu select")
.on("change", change);
//suck in the data, store it in a value called formatted, run the redraw function
d3.csv("/sites/default/d3_files/d3-provinces/statistics-april-15-2.csv", function(data) {
formatted = data;
redraw();
});
d3.select(window)
.on("keydown", function() {
altKey = d3.event.altKey;
})
.on("keyup", function() {
altKey = false;
});
var altKey;
// set terms of transition that will take place
// when a new type (Death etc.)indicator is chosen
function change() {
d3.transition()
.duration(altKey ? 7500 : 1500)
.each(redraw);
} // end change
// REDRAW all the meat goes in the redraw function
function redraw() {
// create data nests based on type indicator (series)
var nested = d3.nest()
.key(function(d) {
return d.type;
})
.map(formatted)
// get value from menu selection
// the option values are set in HTML and correspond
//to the [type] value we used to nest the data
var series = menu.property("value");
// only retrieve data from the selected series, using the nest we just created
var data = nested[series];
// for object constancy we will need to set "keys", one for each type of data (column name) exclude all others.
color.domain(d3.keys(data[0]).filter(function(key) {
return (key !== "date" && key !== "type");
}));
var linedata = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {
name: name,
date: parseDate(d.date),
value: parseFloat(d[name], 10)
};
})
};
});
//make an empty variable to stash the last values into so we can sort the legend // do we need to sort it?
var lastvalues = [];
//setup the x and y scales
var x = d3.time.scale()
.domain([
d3.min(linedata, function(c) {
return d3.min(c.values, function(v) {
return v.date;
});
}),
d3.max(linedata, function(c) {
return d3.max(c.values, function(v) {
return v.date;
});
})
])
.range([0, width]);
var y = d3.scale.linear()
.domain([
d3.min(linedata, function(c) {
return d3.min(c.values, function(v) {
return v.value;
});
}),
d3.max(linedata, function(c) {
return d3.max(c.values, function(v) {
return v.value;
});
})
])
.range([height, 0]);
//will draw the line
var line = d3.svg.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.value);
});
//create and draw the x axis - need to clear the existing axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickPadding(8)
.ticks(10);
//create and draw the y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(0 - width)
.tickPadding(8);
svg.append("svg:g")
.attr("class", "x axis");
svg.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(" + (0) + ",0)")
.call(yAxis);
//bind the data
var thegraph = svg.selectAll(".thegraph")
.data(linedata)
//append a g tag for each line and set of tooltip circles and give it a unique ID based on the column name of the data
var thegraphEnter = thegraph.enter().append("g")
.attr("class", "thegraph")
.attr('id', function(d) {
return d.name + "-line";
})
.style("stroke-width", 2.5)
.on("mouseover", function(d) {
d3.select(this) //on mouseover of each line, give it a nice thick stroke // works
.style("stroke-width", '6px');
var selectthegraphs = $('.thegraph').not(this); //select all the rest of the lines, except the one you are hovering on and drop their opacity
d3.selectAll(selectthegraphs)
.style("opacity", 0.2);
var getname = document.getElementById(d.name); //use get element cause the ID names have spaces in them - not working
var selectlegend = $('.legend').not(getname); //grab all the legend items that match the line you are on, except the one you are hovering on
d3.selectAll(selectlegend) // drop opacity on other legend names
.style("opacity", .2);
d3.select(getname)
.attr("class", "legend-select"); //change the class on the legend name that corresponds to hovered line to be bolder
}) // end of mouseover
.on("mouseout", function(d) { //undo everything on the mouseout
d3.select(this)
.style("stroke-width", '2.5px');
var selectthegraphs = $('.thegraph').not(this);
d3.selectAll(selectthegraphs)
.style("opacity", 1);
var getname = document.getElementById(d.name);
var getname2 = $('.legend[fakeclass="fakelegend"]')
var selectlegend = $('.legend').not(getname2).not(getname);
d3.selectAll(selectlegend)
.style("opacity", 1);
d3.select(getname)
.attr("class", "legend");
});
//actually append the line to the graph
thegraphEnter.append("path")
.attr("class", "line")
.style("stroke", function(d) {
return color(d.name);
})
.attr("d", function(d) {
return line(d.values[0]);
})
.transition()
.duration(2000)
.attrTween('d', function(d) {
var interpolate = d3.scale.quantile()
.domain([0, 1])
.range(d3.range(1, d.values.length + 1));
return function(t) {
return line(d.values.slice(0, interpolate(t)));
};
});
//then append some 'nearly' invisible circles at each data point
thegraph.selectAll("circle")
.data(function(d) {
return (d.values);
})
.enter()
.append("circle")
.attr("class", "tipcircle")
.attr("cx", function(d, i) {
return x(d.date)
})
.attr("cy", function(d, i) {
return y(d.value)
})
.attr("r", 3) // was 12
.style('opacity', .2)
.attr("title", maketip);
//append the legend
var legend = svg.selectAll('.legend')
.data(linedata);
var legendEnter = legend
.enter()
.append('g')
.attr('class', 'legend')
.attr('id', function(d) {
return d.name;
})
.on('click', function(d) { //onclick function to toggle off the lines
if ($(this).css("opacity") == 1) {
//uses the opacity of the item clicked on to determine whether to turn the line on or off
var elemented = document.getElementById(this.id + "-line"); //grab the line that has the same ID as this point along w/ "-line"
//use get element cause ID has spaces
d3.select(elemented)
.transition()
.duration(1000)
.style("opacity", 0)
.style("display", 'none');
d3.select(this)
.attr('fakeclass', 'fakelegend')
.transition()
.duration(1000)
.style("opacity", .2);
} else {
var elemented = document.getElementById(this.id + "-line");
d3.select(elemented)
.style("display", "block")
.transition()
.duration(1000)
.style("opacity", 1);
d3.select(this)
.attr('fakeclass', 'legend')
.transition()
.duration(1000)
.style("opacity", 1);
}
});
//create a scale to pass the legend items through // this is broken for some types
var legendscale = d3.scale.ordinal()
.domain(lastvalues)
.range([0, 30, 60, 90, 120, 150, 180, 210]);
//actually add the circles to the created legend container
legendEnter.append('circle')
.attr('cx', width + 20) // cx=width+50 made circle overlap text
.attr('cy', function(d) {
var newScale = (legendscale(d.values[d.values.length - 1].value) + 20);
return newScale;
})
.attr('r', 7)
.style('fill', function(d) {
return color(d.name);
});
//add the legend text
legendEnter.append('text')
.attr('x', width + 35) // is this an issue?
.attr('y', function(d) {
return legendscale(d.values[d.values.length - 1].value);
})
.text(function(d) {
return d.name;
});
// set variable for updating visualization
var thegraphUpdate = d3.transition(thegraph);
// change values of path and then the circles to those of the new series
thegraphUpdate.select("path")
.attr("d", function(d, i) {
lastvalues[i] = d.values[d.values.length - 1].value;
lastvalues.sort(function(a, b) {
return b - a
});
legendscale.domain(lastvalues);
return line(d.values);
// }
});
thegraphUpdate.selectAll("circle")
.attr("title", maketip) // displays HTML but not circle
.attr("cy", function(d, i) {
return y(d.value)
})
.attr("cx", function(d, i) {
return x(d.date)
});
// and now for legend items
var legendUpdate = d3.transition(legend);
legendUpdate.select("circle")
.attr('cy', function(d, i) {
return legendscale(d.values[d.values.length - 1].value);
});
legendUpdate.select("text")
.attr('y', function(d) {
return legendscale(d.values[d.values.length - 1].value);
});
d3.transition(svg).select(".x.axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
//make my tooltips work
$('circle').tipsy({
opacity: .9,
gravity: 'n',
html: true
});
//end of the redraw function
}
svg.append("svg:text")
.attr("text-anchor", "start")
.attr("x", 0 - margin.left)
.attr("y", height + margin.bottom - 10)
.text(sourcetext)
.attr("class", "source");
My adapted code (including a lot of console.log messages) is in jsfiddle https://jsfiddle.net/pwarwick43/13fpn567/2/
I am beginning to think the problem might be with the version of d3 or jquery. Anyone got suggestions about this?
I am developing a scatterplot in d3 where you can alter both x axis and y axis using a dropdown menu.
I was able to draw the grid lines but my problem is redrawing them when a new value is picked for either x axis or y axis.
I hope someone could advise me what I should do to make this happen.
Here is my code up until now(js):
d3.csv('data.csv',function (data) {
// CSV section
var body = d3.select('body')
var selectData = [ { "text" : "Trust words" },
{ "text" : "Surprise words" },
{ "text" : "Sadness words" },
{ "text" : "Positive words"},
{ "text" : "Negative words"},
{ "text" : "Fear words"},
{ "text" : "Disgust words"},
{ "text" : "Anticipation words"},
{ "text" : "Anger words"},
]
// Select X-axis Variable
var span = body.append('span')
.text('Select an Emotion word for the Horizontal scale: ')
var xInput = body.append('select')
.attr('id','xSelect')
.on('change',xChange)
.selectAll('option')
.data(selectData)
.enter()
.append('option')
.attr('value', function (d) { return d.text })
.text(function (d) { return d.text ;})
body.append('br')
body.append('br')
// Select Y-axis Variable
var span = body.append('span')
.text('Select an Emotion word for the Vertical scale: ')
var yInput = body.append('select')
.attr('id','ySelect')
.on('change',yChange)
.selectAll('option')
.data(selectData)
.enter()
.append('option')
.attr('value', function (d) { return d.text })
.text(function (d) { return d.text ;})
body.append('br')
// Variables
var body = d3.select('body')
var margin = { top: 50, right: 50, bottom: 50, left: 50 }
var h = 700 - margin.top - margin.bottom
var w = 1350 - margin.left - margin.right
var rscale = d3.scale.linear()
// Scales
var cValue = function(d) { if (parseFloat(d['Emotions words']) >=0 && parseFloat(d['Emotions words']) <= 200000) return 'Emotion Words NO: 0-200,000 inc'
else if(parseFloat(d['Emotions words']) >200000 && parseFloat(d['Emotions words']) <=350000) return 'Emotion Words NO: 200,001-350,000 inc'
else return 'Emotion words NO: >350,000'},
color = d3.scale.category10();
var xScale = d3.scale.linear()
.domain([
d3.min([0,d3.min(data,function (d) { return parseFloat(d['Trust words']) })]),
d3.max([0,d3.max(data,function (d) { return parseFloat(d['Trust words']) })])
])
.range([0,w])
var yScale = d3.scale.linear()
.domain([
d3.min([0,d3.min(data,function (d) { return parseFloat(d['Trust words']) })]),
d3.max([0,d3.max(data,function (d) { return parseFloat(d['Trust words']) })])
])
.range([h,0])
// SVG
var svg = body.append('svg')
.attr('height',h + margin.top + margin.bottom)
.attr('width',w + margin.left + margin.right)
.append('g')
.attr('transform','translate(' + margin.left + ',' + margin.top + ')')
// X-axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.ticks(5)
// Y-axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient('left')
.ticks(5)
function make_x_axis() {
return d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5)
}
function make_y_axis() {
return d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5)
}
// Circles
var circles = svg.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx',function (d) { return xScale(d['Trust words']) })
.attr('cy',function (d) { return yScale(d['Trust words']) })
.attr('r',function (d) { return rscale(d['Average_movie_rating'])*2;})
.attr('stroke','black')
.attr('stroke-width',1)
.attr('fill',function (d) { return color(cValue(d)); })
.on('mouseover', function () {
d3.select(this)
.transition()
.duration(500)
.attr('r',20)
.attr('stroke-width',3)
})
.on('mouseout', function () {
d3.select(this)
.transition()
.duration(500)
.attr('r',10)
.attr('stroke-width',1)
})
.append('title') // Tooltip
.text(function (d) { return 'Actor Name: ' + d['Actor_ Name'] +
'\nTrust words: ' + d['Trust words'] +
'\nSurprise words: ' + d['Surprise words']+
'\nSadness words: ' + d['Sadness words'] +
'\nPositive words: ' + d['Positive words'] +
'\nNegative words: ' + d['Negative words'] +
'\nFear words: ' + d['Fear words'] +
'\nDisgust words: ' + d['Disgust words'] +
'\nAnticipation words: ' + d['Anticipation words'] +
'\nAnger words: ' + d['Anger words'] +
'\nAverage ranking: '+ d['Average_movie_rating']})
// X-axis
svg.append('g')
.attr('class','axis')
.attr('id','xAxis')
.attr('transform', 'translate(0,' + h + ')')
.call(xAxis)
.append('text') // X-axis Label
.attr('id','xAxisLabel')
.attr('y',-10)
.attr('x',w)
.attr('dy','.71em')
.style('text-anchor','end')
.text('Trust words')
// Y-axis
svg.append('g')
.attr('class','axis')
.attr('id','yAxis')
.call(yAxis)
.append('text') // y-axis Label
.attr('id', 'yAxisLabel')
.attr('transform','rotate(-90)')
.attr('x',0)
.attr('y',5)
.attr('dy','.71em')
.style('text-anchor','end')
.text('Trust words')
svg.append('g')
.attr("class", "grid")
.attr("transform", "translate(0," + h + ")")
.call(make_x_axis()
.tickSize(-h, 0, 0)
.tickFormat("")
)
svg.append('g')
.attr("class", "grid")
.call(make_y_axis()
.tickSize(-w, 0, 0)
.tickFormat("")
)
function yChange() {
var value = this.value // get the new y value
yScale // change the yScale
.domain([
d3.min([0,d3.min(data,function (d) { return parseFloat(d[value]) })]),
d3.max([0,d3.max(data,function (d) { return parseFloat(d[value]) })])
])
yAxis.scale(yScale) // change the yScale
d3.select('#yAxis') // redraw the yAxis
.transition().duration(1000)
.call(yAxis)
d3.select('#yAxisLabel') // change the yAxisLabel
.text(value)
d3.selectAll('circle') // move the circles
.transition().duration(1000)
.delay(function (d,i) { return i*100})
.attr('cy',function (d) { return yScale(d[value]) })
}
function xChange() {
var value = this.value // get the new x value
xScale // change the xScale
.domain([
d3.min([0,d3.min(data,function (d) { return parseFloat(d[value]) })]),
d3.max([0,d3.max(data,function (d) { return parseFloat(d[value]) })])
])
xAxis.scale(xScale) // change the xScale
d3.select('#xAxis') // redraw the xAxis
.transition().duration(1000)
.call(xAxis)
d3.select('#xAxisLabel') // change the xAxisLabel
.transition().duration(1000)
.text(value)
d3.selectAll('circle') // move the circles
.transition().duration(1000)
.delay(function (d,i) { return i*100})
.attr('cx',function (d) { return xScale(d[value]) })
}
var legend = svg.selectAll(".legend")
.data(color.domain())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 25 + ")"; });
// draw legend colored rectangles
legend.append("rect")
.attr("x", w + 25)
.attr("y", 490)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
// draw legend text
legend.append("text")
.attr("x", w - 24)
.attr("y", 500)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d;})
.text(function(d) { return d;})
})
Thanks
I'm sorry, I wanted to write a comment, but I don't have enough reputation so I have to write this as an answer. Is there any chance you can provide a mini dataset so that I can run this code in my machine? It's easier to understand how the code is supposed to work if I have a dataset to run it with.
Also, what do you mean by gridlines? If you mean the ticks, then I think those won't change no matter what scale you use. You set it to 5 and so I think there will always be 5 evenly spaced tick marks.
I have a scatter plot which shows a trendline/average line based on the data provided. The only problem is that the trendline bleeds into the y axis label (see picture).
Here is my d3 code, how can I "trim" the trendline to fit only the plotted region of the chart?
scatterPlot.js
jQuery.sap.require("sap/ui/thirdparty/d3");
jQuery.sap.declare("pricingTool.ScatterPlot");
sap.ui.core.Element.extend("pricingTool.ScatterPlotItem", { metadata : {
properties : {
"quarter" : {type : "string", group : "Misc", defaultValue : null},
"values" : {type : "object", group : "Misc", defaultValue : null}
}
}});
sap.ui.core.Control.extend("pricingTool.ScatterPlot", {
metadata : {
properties: {
"title": {type : "string", group : "Misc", defaultValue : "ScatterPlot Title"}
},
aggregations : {
"items" : { type: "pricingTool.ScatterPlotItem", multiple : true, singularName : "item"}
},
defaultAggregation : "items",
events: {
"onPress" : {},
"onChange":{}
}
},
init: function() {
//console.log("vizConcept.ScatterPlot.init()");
this.sParentId = "";
},
createScatterPlot : function() {
//console.log("vizConcept.ScatterPlot.createScatterPlot()");
var oScatterPlotLayout = new sap.m.VBox({alignItems:sap.m.FlexAlignItems.Center,justifyContent:sap.m.FlexJustifyContent.Center});
var oScatterPlotFlexBox = new sap.m.FlexBox({height:"auto",alignItems:sap.m.FlexAlignItems.Center});
/* ATTENTION: Important
* This is where the magic happens: we need a handle for our SVG to attach to. We can get this using .getIdForLabel()
* Check this in the 'Elements' section of the Chrome Devtools:
* By creating the layout and the Flexbox, we create elements specific for this control, and SAPUI5 takes care of
* ID naming. With this ID, we can append an SVG tag inside the FlexBox
*/
this.sParentId=oScatterPlotFlexBox.getIdForLabel();
oScatterPlotLayout.addItem(oScatterPlotFlexBox);
return oScatterPlotLayout;
},
/**
* The renderer render calls all the functions which are necessary to create the control,
* then it call the renderer of the vertical layout
* #param oRm {RenderManager}
* #param oControl {Control}
*/
renderer: function(oRm, oControl) {
var layout = oControl.createScatterPlot();
oRm.write("<div");
oRm.writeControlData(layout); // writes the Control ID and enables event handling - important!
oRm.writeClasses(); // there is no class to write, but this enables
// support for ColorBoxContainer.addStyleClass(...)
oRm.write(">");
oRm.renderControl(layout);
oRm.addClass('verticalAlignment');
oRm.write("</div>");
},
onAfterRendering: function(){
//console.log("vizConcept.ScatterPlot.onAfterRendering()");
//console.log(this.sParentId);
var cItems = this.getItems();
var data = [];
for (var i=0;i<cItems.length;i++){
var oEntry = {};
for (var j in cItems[i].mProperties) {
oEntry[j]=cItems[i].mProperties[j];
}
data.push(oEntry);
}
$("svg").last().remove();
/*
* ATTENTION: See .createScatterPlot()
* Here we're picking up a handle to the "parent" FlexBox with the ID we got in .createScatterPlot()
* Now simply .append SVG elements as desired
* EVERYTHING BELOW THIS IS PURE D3.js
*/
var margin = {
top: 25,
right: 30,
bottom: 80,
left: 90
},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var tableData = data[0].values;
var dates = [];
for(var i = 0; i<tableData.length; i++){
dates[i] = new Date(tableData[i].date);
dates.sort(function(a,b) {
return a -b;
})
}
var minDate = dates[0],
maxDate = dates[dates.length-1];
//test//
var year = new Date(dates[0]);
minDate.setMonth(year.getMonth(), -2);
var year = new Date(dates[dates.length-1]);
console.log(year);
//maxDate.setMonth(year.getMonth(), 6);
console.log(maxDate);
//end test//
// Our X scale
//var x = d3.scale.linear()
var x = d3.time.scale()
.domain([minDate, maxDate])
.range([0, width]);
// Our Y scale
var y = d3.scale.linear()
.range([height, 0]);
// Our color bands
var color = d3.scale.ordinal()
.range(["#000", "#004460", "#0070A0", "#008BC6", "#009DE0", "#45B5E5", "8CCDE9", "#DAEBF2"]); //"#00A6ED",
// Use our X scale to set a bottom axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(8)
.tickFormat(d3.time.format("%b-%Y"));
// Same for our left axis
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var tip = d3.select("body").append("div")
.attr("class", "sctooltip")
.style("position", "absolute")
.style("text-align", "center")
.style("width", "80px")
.style("height", "42px")
.style("padding", "2px")
.style("font", "11px sans-serif")
.style("background", "#F0F0FF")
.style("border", "0px")
.style("border-radius", "8px")
.style("pointer-events", "none")
.style("opacity", 0);
var vis = d3.select("#" + this.sParentId);
var svg = vis.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style("background-color","white")
.style("font", "12px sans-serif")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain([minDate, maxDate]);
// Our Y domain is from zero to our highest total
y.domain([0, d3.max(data, function (d) {
var max = d3.max(d.values, function (dd){
return(+dd.price);
})
return max;
})]);
var totalval = 0;
var totalval2 = 0;
var values = 0;
data.forEach(function (d) {
d.values.forEach(function (dd){
values +=1;
totalval += +dd.date;
totalval2 += +dd.price;
});
});
var priceAverage = totalval2/values;
var average = totalval/totalval2;
var value = data[0].values[0].price;
var line_data = [
{"x": 0, "y": y.domain()[0]},
{"x": y.domain()[1]*average, "y": y.domain()[1]}
];
var avgline = d3.svg.line()
.x(function(d){ return x(d.x); })
.y(function(d){ return y(d.y); })
.interpolate("linear");
svg.append("g")
.attr("class", "x axis")
.style("fill", "none")
.style("stroke", "grey")
.style("shape-rendering", "crispEdges")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-25)" );
svg.append("g")
.attr("class", "y-axis")
.style("fill", "none")
.style("stroke", "grey")
.style("shape-rendering", "crispEdges")
.call(yAxis);
//average line
svg.append("path")
.attr("class", "avgline")
.style("stroke", "#000")
.style("stroke-width", "1px")
.style("stroke-dasharray", ("4, 4"))
.attr("d", avgline(line_data));
var plot = svg.selectAll(".values") //changed this from quarter
.data(data)
.enter().append("g");
plot.selectAll("dot")
.data(function (d) {
return d.values;
})
.enter().append("circle")
.attr("class", "dot")
.attr("r", 5)
.attr("cx", function (d){
return x(d.date);
})
.attr("cy", function (d) {
return y(d.price);
})
.style("stroke", "#004460")
.style("fill", function (d) {
return color(d.name);
})
.style("opacity", .9)
.style("visibility", function(d){
if(+d.date != 0){
return "visible";
}else{
return "hidden";
}
})
.style("pointer-events", "visible")
.on("mouseover", function(d){
tip.transition()
.duration(200)
.style("opacity", .8);
tip.html(d.name + "<br/>" + d.quarter + "<br />" + "Avg. " +(+d.date/+d.price).toFixed(2))
.style("left", (d3.event.pageX-40) + "px")
.style("top", (d3.event.pageY-50) + "px");
})
.on("mouseout", function(d){
tip.transition()
.duration(500)
.style("opacity", 0);
});;
// var legend = svg.selectAll(".legend")
// .data(color.domain())
// .enter().append("g")
// .attr("class", "legend")
// .attr("transform", function (d, i) {
// return "translate(0," + i * 16 + ")";
// });
// legend.append("rect")
// .attr("x", width - 12)
// .attr("width", 12)
// .attr("height", 12)
// .style("fill", color);
// legend.append("text")
// .attr("x", width - 24)
// .attr("y", 6)
// .attr("dy", ".35em")
// .style("text-anchor", "end")
// .style("font", "11px sans-serif")
// .text(function (d) {
// return d;
// });
//y-axis label
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", - (height/2))
.attr("y", 10 - margin.left)
.attr("dy", "1em")
.style("text-anchor", "middle")
.style("font", "16px sans-serif")
.text("PPI Cost($)");
//x-axis label
svg.append("text")
.attr("transform", "translate("+(width/2) +","+ (height +margin.top +50)+")")
.style("text-anchor", "middle")
.style("font", "16px sans-serif")
.text("Purchase Date");
var avglabel = svg.append("g")
.attr("transform", "translate(" + (width-40) + ",140)");
avglabel.append("text")
.style("text-anchor", "middle")
.text("Average: $" + priceAverage.toFixed(2));
}
});
Try setting the x values to the start and end of the graph:
var line_data = [
{x: x.domain()[0], y: average},
{x: x.domain()[1], y: average}
];
Alternatively you could set the path string directly without using a path generator:
svg.append("path")
.attr("class", "avgline")
.style("stroke", "#000")
.style("stroke-width", "1px")
.style("stroke-dasharray", ("4, 4"))
.attr("d", [
"M", x.range()[0], y(average),
"H", x.range()[1]
].join(' '))
I have created scattered chart in D3.
It's working fine but I have a requirement to add zooming and axis rescaling to the chart.
Since I am pretty much new to d3 I am not able to do it.I have seen some example about it but I am able apply the zooming, panning etc code in my chart.
Here is my code-
var margin = {
top: 35,
right: 10,
bottom: 40,
left: 80
},
width = width - margin.left - margin.right,
height = height - margin.top - margin.bottom;
var xValue = function(d){
return d[measureArray[1]];
},
x = d3.scale.linear()
.range([0, width*.98]),
xMap = function(d,i) {
return x(xValue(d));
},
make_x_axis = function() {
return d3.svg.gridaxis()
.scale(x)
.orient("bottom")
},
xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(function(d) {
return d;
});
var yValue = function(d){
return d[measureArray[0]];
},
y = d3.scale.linear()
.range([height*.98, 0]),
yMap = function(d,i){
return y(yValue(d));
},
make_y_axis = function() {
return d3.svg.gridaxis()
.scale(y)
.orient("left")
},
yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(function(d) {
// if(typeof displayY !=="undefined" && displayY =="Yes"){
// if(yAxisFormat==""){
return d;
});
var zValue = function(d){
return d[measureArray[2]];
},
zScale = d3.scale.linear()
.range([height*.98, 0]),
zMap = function(d) {
return zScale(zValue(d));
};
var color = d3.scale.category10();
var svg = d3.select("body") .append("svg")
.attr("id", "svg_" + div)
.attr("viewBox", "0 0 "+(width + margin.left + margin.right)+" "+(height + margin.top + margin.bottom+ 17.5 )+" ")
.classed("svg-content-responsive", true)
.append("g")
.attr("transform", "translate(" + (margin.left*.7) + "," + (margin.top+3) + ")");
var tooltip = d3.select("#"+divId).append("div")
.attr("class", "tooltip")
.style("opacity", 0);
data.forEach(function(d) {
d[measureArray[2]] = +d[measureArray[2]]
d[measureArray[1]] = +d[measureArray[1]];
d[measureArray[0]] = +d[measureArray[0]];
});
x.domain([d3.min(data, xValue)-1, d3.max(data, xValue)+1]);
y.domain([d3.min(data, yValue)-1, d3.max(data, yValue)+1]);
// }
if(typeof chartData[divId]["displayX"]!="undefined" && chartData[divId]["displayX"]!="" && chartData[divId]["displayX"]!="Yes"){}else{
svg.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(measureArray[1]);
}
// y-axis
if(typeof chartData[divId]["displayY"]!="undefined" && chartData[divId]["displayY"]!="" && chartData[divId]["displayY"]!="Yes"){}else{
svg.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(measureArray[0]);
}
var max = maximumValue(data, measureArray[2]);
var min = minimumValue(data, measureArray[2]);
var temp = {};
temp["min"] = min;
temp["max"] = max;
svg.selectAll(".circle")
.data(data)
.enter().append("circle")
.attr("index_value", function(d, i) {
return "index-" + d[columns[1]].replace(/[^a-zA-Z0-9]/g, '', 'gi');
})
.attr("class", function(d, i) {
return "bars-Bubble-index-" + d[columns[1]].replace(/[^a-zA-Z0-9]/g, '', 'gi')+div;
})
.attr("r", function(d,i){
// var scale = d3.scale.linear().domain([temp["max"], temp["min"]]).range(["38", "12"]);
// var radius = scale(data[i][measureArray[2]]);
return 6;
})
.attr("cx", xMap)
.attr("cy", yMap)
.attr("opacity",".6")
.attr("fill", 'red')
.attr("id",function(d,i) {
return d[columns[0]]+":"+d[columns[1]];
})
.attr("onclick", fun);
Working fiddle.
You can do it like this:
//define zoom behavior
var zoom = d3.behavior.zoom()
.on("zoom", draw);
//make a rectangle for receiving all the zoom/pan action.
svg.append("rect")
.attr("class", "pane")
.attr("width", width)
.attr("height", height)
.call(zoom);
//make a clip path so that the circle don't go out of the graph.
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("x", x(0))
.attr("y", y(1))
.attr("width", x(1) - x(0))
.attr("height", y(0) - y(1));
Add the following class to the style(so that the rectangle pane is not visible) note: that the fill is none:
rect.pane {
cursor: move;
fill: none;
pointer-events: all;
}
After defining the domain, set the zoom x and y
x.domain([d3.min(data, xValue) - 1, d3.max(data, xValue) + 1]);
y.domain([d3.min(data, yValue) - 1, d3.max(data, yValue) + 1]);
// set the zoom for x and y
zoom.x(x);
zoom.y(y);
Make a group for all the circle so that its within the clippath
circlegroup = svg.append("g").attr("clip-path", "url(#clip)");
circlegroup.selectAll(".circle")...
Define the draw function which will be called on zoom and pan:
function draw() {
svg.select("g.x.axis").call(xAxis);//zoom of x axis
svg.select("g.y.axis").call(yAxis);//zoom of y axis
//update the position of the circle on zoom/pan
svg.selectAll("circle").attr("cx", xMap)
.attr("cy", yMap)
}
working code here
Need some help figuring out how to auto update 3djs scatter plot. The code looks fine ,however, when the update
function is running the graph gets updated but the scatter plot remains at place. I'm using svg.selectAll(".dot").remove() in order to remove the outdated ones but unable to find a way to added them back. I found a few solutions online but none of them worked for my code.
Any help would be much appreciated. thanks
DB structure:
dtg | temperature
2016-03-02 09:14:00 23
2016-03-02 09:10:00 22
Code:
<script>
// Set the dimensions of the canvas / graph
var margin = {top: 30, right: 20, bottom: 30, left: 40},
width = 400 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
// Parse the date / time
var parseDate = d3.time.format("%Y-%m-%d %H:%M:%S").parse;
var formatTime = d3.time.format("%e %B %X");
// Set the ranges
var x = d3.time.scale().range([0, width]);
var y = d3.scale.linear().range([height, 0]);
// Define the axes
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(5);
var yAxis = d3.svg.axis().scale(y)
.orient("left").ticks(5);
// Define the line
var valueline = d3.svg.line()
.x(function(d) { return x(d.dtg); })
.y(function(d) { return y(d.temperature); });
var div = d3.select("#chart1").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// Adds the svg canvas
var svg = d3.select("#chart1")
.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 + ")");
function make_x_axis() {
return d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(10)
}
function make_y_axis() {
return d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10)
}
// Get the data
d3.json("2301data.php", function(error, data) {
data.forEach(function(d) {
d.dtg = parseDate(d.dtg);
d.temperature = +d.temperature;
});
// Scale the range of the data
x.domain(d3.extent(data, function(d) { return d.dtg; }));
y.domain([0, 60]); //
// y.domain([0, d3.max(data, function(d) { return d.temperature; })]);
// Add the valueline path.
svg.append("path")
.attr("class", "line")
.attr("d", valueline(data));
// draw the scatterplot
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.filter(function(d) { return d.temperature > 30 })
.style("fill", "red")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.dtg); })
.attr("cy", function(d) { return y(d.temperature); })
// Tooltip stuff after this
.on("mouseover", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
div.transition()
.duration(200)
.style("opacity", .9);
div .html(
d.temperature + "C" + "<br>" +
formatTime(d.dtg))
.style("left", (d3.event.pageX + 8) + "px")
.style("top", (d3.event.pageY - 18) + "px");})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
// Add the X Axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.style("font-size", "14px")
.call(xAxis);
// Add the Y Axis
svg.append("g")
.attr("class", "y axis")
.style("font-size", "14px")
.call(yAxis);
// Draw the grid 1
svg.append("g")
.attr("class", "grid")
.attr("transform", "translate(0," + height + ")")
.call(make_x_axis()
.tickSize(-height, 0, 0)
.tickFormat("")
)
// Draw the grid 2
svg.append("g")
.attr("class", "grid")
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat("")
)
// Addon 3 // text label for the graph
svg.append("text")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "14px")
.style("text-decoration", "underline")
.style('fill', 'white')
//.attr("class", "shadow") // using text css
.text("2301 Temperature read in the past 24h\n");
});
var inter = setInterval(function() {
updateData();
}, 5000);
// ** Update data section (Called from the onclick)
function updateData() {
// Get the data again
d3.json("2301data.php", function(error, data) {
data.forEach(function(d) {
d.dtg = parseDate(d.dtg);
d.temperature = +d.temperature;
//d.hum = +d.hum; // Addon 9 part 3
});
// Scale the range of the data again
x.domain(d3.extent(data, function(d) { return d.dtg; }));
y.domain([0, 60]);
var svg = d3.select("#chart1").transition();
// Make the changes
svg.selectAll(".dot").remove(); //remove old dots
svg.select(".line").duration(750).attr("d", valueline(data));
svg.select("x.axis").duration(750).call(xAxis);
svg.select("y.axis").duration(750).call(yAxis);
//update the scatterplot
svg.selectAll(".dotUpdate")
.data(data)
.attr("class", "dotUpdate")
.enter().append("circle")
.filter(function(d) { return d.temperature > 30 })
.style("fill", "red")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.dtg); })
.attr("cy", function(d) { return y(d.temperature); });
});
}
</script>
The first thing I did wrong was using the wrong d3js.. the following line has to be replaced
<script src="http://d3js.org/d3.v3.min.js"></script>
With the following or else svg.selectAll would not work.
<script type="text/javascript" src="http://d3js.org/d3.v3.js"></script>
Now, as far as the scatter plots update goes. I'm now using the code below which works fine with some databases. In my case it still does not work well and I'll be posting it as a sepearte question as stakoverflow guidlines requsts..
// ** Update data section (Called from the onclick)
function updateData() {
// Get the data again
data = d3.json("2301data.php", function(error, data) {
data.forEach(function(d) {
d.dtg = parseDate(d.dtg);
d.temperature = +d.temperature;
// d.hum = +d.hum; // Addon 9 part 3
});
// Scale the range of the data again
x.domain(d3.extent(data, function(d) { return d.dtg; }));
y.domain([0, 60]); // Addon 9 part 4
var svg = d3.select("#chart1")
var circle = svg.selectAll("circle").data(data)
svg.select(".x.axis") // change the x axis
.transition()
.duration(750)
.call(xAxis);
svg.select(".y.axis") // change the y axis
.transition()
.duration(750)
.call(yAxis);
svg.select(".line") // change the line
.transition()
.duration(750)
.attr("d", valueline(data));
circle.transition()
.duration(750)
.attr("cx", function(d) { return x(d.dtg); })
// enter new circles
circle.enter()
.append("circle")
.filter(function(d) { return d.temperature > 30 })
.style("fill", "red")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.dtg); })
// remove old circles
circle.exit().remove()
});
}