Changing color scale of heat-map dynamically - javascript

I am trying to add color options for my heat-map visualization. I have a predefined colors array at the beginning, and I draw rectangles like this:
plotChart.selectAll(".cell")
.data(data)
.enter().append("rect")
.attr("class", "cell")
.attr("x", function (d) { return x(d.timestamp); })
.attr("y", function (d) { return y(d.hour); })
.attr("width", function (d) { return x(d3.timeWeek.offset(d.timestamp, 1)) - x(d.timestamp); })
.attr("height", function (d) { return y(d.hour + 1) - y(d.hour); })
.attr("fill", function (d) { return colorScale(d.value); });
When I click a link in a dropdown menu, I do this:
$(".colorMenu").click(function (event) {
event.preventDefault();
// remove # from clicked link
var addressValue = $(this).attr("href").substring(1);
// get color scheme array
var newColorScheme = colorDict[addressValue];
// update color scale range
colorScale.range(newColorScheme);
// here I need to repaint with colors
});
My color scale is quantile scale, so I cannot use invert function to find values of each rectangle. I don't want to read the data again because it would be a burden, so how can I change fill colors of my rectangles?

I don't want to read the data again...
Well, you don't need to read the data again. Once the data was bound to the element, the datum remains there, unless you change/overwrite it.
So, this can simply be done with:
.attr("fill", d => colorScale(d.value));
Check this demo:
var width = 500,
height = 100;
var ranges = {};
ranges.range1 = ['#f7fbff','#deebf7','#c6dbef','#9ecae1','#6baed6','#4292c6','#2171b5','#08519c','#08306b'];
ranges.range2 = ['#fff5eb','#fee6ce','#fdd0a2','#fdae6b','#fd8d3c','#f16913','#d94801','#a63603','#7f2704'];
ranges.range3 = ['#f7fcf5','#e5f5e0','#c7e9c0','#a1d99b','#74c476','#41ab5d','#238b45','#006d2c','#00441b'];
ranges.range4 = ['#fff5f0','#fee0d2','#fcbba1','#fc9272','#fb6a4a','#ef3b2c','#cb181d','#a50f15','#67000d'];
var color = d3.scaleQuantile()
.domain([0, 15])
.range(ranges.range1);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var data = d3.range(15);
var rects = svg.selectAll(".rects")
.data(data)
.enter()
.append("rect");
rects.attr("y", 40)
.attr("x", d => d * 25)
.attr("height", 20)
.attr("width", 20)
.attr("stroke", "gray")
.attr("fill", d => color(d));
d3.selectAll("button").on("click", function() {
color.range(ranges[this.value]);
rects.attr("fill", d => color(d))
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<button value="range1">Range1</button>
<button value="range2">Range2</button>
<button value="range3">Range3</button>
<button value="range4">Range4</button>

Related

d3 x.invert returning Invalid Date from d3.pointer. d3 v6

I probably should be sticking with d3 v4 or lower it seems. Anyway, I'm trying to create a typical Area Chart to display stock data. But I'm running into a problem with a mouseover function where I try to get the nearest data point based off mouse location. Similar to what happens when you hover on a graph like on google or yahoo finance. But the main issue is a problem with my X variable I guess.
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var bisect = d3.bisector(function (d) {
return d.date;
}).left;
// Create the circle that travels along the curve of chart
var area = d3
.area()
.x(function (d) {
return x(d.date);
})
.y0(height)
.y1(function (d) {
return y(d.close);
});
var 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 + ")");
var focus = svg
.append("g")
.append("circle")
.style("fill", "none")
.attr("stroke", "black")
.attr("r", 8.5)
.style("opacity", 0);
function mouseover() {
focus.style("opacity", 1);
}
// format the data
var stockDateObj = {};
for (const [key, value] of Object.entries(data)) {
var stockDateObj = {};
stockDateObj.date = parseTime(key);
stockDateObj.close = Number(value["4. close"]);
setD3Data.push(stockDateObj);
}
setD3Data = setD3Data.sort(function (x, y) {
return d3.ascending(x.date, y.date);
});
this.setState({ currD3Data: setD3Data });
x.domain(
d3.extent(this.state.currD3Data, function (d) {
return d.date;
})
);
y.domain([
0,
d3.max(this.state.currD3Data, function (d) {
return d.close;
}),
]);
function mousemove(e) {
// recover coordinate we need
console.log(d3.pointer(e, svg.node()));
var x0 = x.invert(d3.pointer(e, svg.node()));
console.log(x0);
var i = bisect(this.state.currD3Data, x0, 1);
console.log(i);
var selectedData = this.state.currD3Data[i];
console.log(selectedData);
focus.attr("cx", x(selectedData.date)).attr("cy", y(selectedData.close));
}
function mouseout() {
focus.style("opacity", 0);
}
mouseover = mouseover.bind(this);
mousemove = mousemove.bind(this);
mouseout = mouseout.bind(this);
svg
.append("rect")
.style("fill", "none")
.style("pointer-events", "all")
.attr("width", width)
.attr("height", height)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseout", mouseout);
The data above shows the creation of the x domain and the svg element that handles the mouseover functionality.
I don't really know what else to do considering I'm using d3 v6.
But this my error when i console.log(x0):
Invalid Date

How to show different titles for different chart in D3js?

I am new to Javascript and i am trying to draw 4 radar charts. Each chart has different title. I create TitleOptions var and call it below. but it shows everything on every chart. Can I filter the title by using ChartID? I attached my code below. and could anyone help me with this?
<script>
var w = 200;
var h = 200;
var colorscale = d3.scale.category10();
//Legend, titles
var LegendOptions = ['Try Count','Succcess Count', 'Success Rate'];
var TitleOptions=['Try/Success Count Per Skill', 'Try/Success Rate Per Skill', 'Point Get Per Skill', 'Point Lose Per Skill']
////////////////////////////////////////////
/////////// Initiate legend ////////////////
////////////////////////////////////////////
var svg = d3.select('#body')
// .selectAll('svg')
.append('svg')
.attr("width", w+300)
.attr("height", h)
//Create the title for the legend
var text = svg.append('g').append("text")
.attr("class", "title")
.attr('transform', 'translate(90,0)')
.attr("x", 30)
.attr("y", 10)
.attr("font-size", "12px")
.attr("fill", "#404040")
// .text("What % of owners use a specific service in a week");
.text(TitleOptions);
//Initiate Legend
var legend = svg.append("g")
.attr("class", "legend")
.attr("height", 100)
.attr("width", 200)
.attr('transform', 'translate(90,20)')
;
//Create colour squares
legend.selectAll('rect')
.data(LegendOptions)
.enter()
.append("rect")
.attr("x", w - 65)
.attr("y", function(d, i){ return i * 20;})
.attr("width", 10)
.attr("height", 10)
.style("fill", function(d, i){ return colorscale(i);})
;
//Create text next to squares
legend.selectAll('text')
.data(LegendOptions)
.enter()
.append("text")
.attr("x", w - 52)
.attr("y", function(d, i){ return i * 20 + 9;})
.attr("font-size", "11px")
.attr("fill", "#737373")
.text(function(d) { return d; })
;
//Options for the Radar chart, other than default
var mycfg = {
w: w,
h: h,
maxValue: 0.6,
levels: 6,
ExtraWidthX: 300
}
//Load the data and Call function to draw the Radar chart
// dynamic data creation
d3.json("<c:url value='/chart/radarChartData.do?ChartID=${ChartID}&PlayerKey=${PlayerKey}'/>", function(error, data){
RadarChart.draw("#chart", JSONconverter(data.list), mycfg);
});
</script>
Encapsulate the drawing part into a function and call it four times. Something like:
function draw(title) {
const svg = ..
..
.text(title);
}
draw('title1');
draw('title2');
// or better:
['title1', 'title2'].forEach(draw);

How do I make a scatter plot of lines in D3?

I have a series of paired xy coordinates that create 58 lines. I want to plot them on a Cartesian graph, values are between -5 and 5 on both axis, essentially making a scatter plot of lines. I have made something similar in matplotlib using the quiver function, but I want to be able to do this in D3. I would also like to be able to label each line, or each line that meets a length threshold. The code I have come up with below. Thanks.
var lisa = [["Eloy",0.0169808,-0.695317,-0.0510301,-0.6995938],
["Florence",-0.3465685,-0.6790588,-0.5869514,-0.6762134],
["Phoenix",0.677068,-0.5754814,-0.6052215,-0.6158059],
["Tucson",-0.663848,0.4111043,-0.6722116,0.011639]]
var w = 200;
var h = 200;
//create the svg element and set the height and width parameters
var svg = d3.select("div").select("div")
.append("svg")
.attr("height",h)
.attr("width", w)
.style("border", "1px solid black");
//Create the scale for the scatter plot
var xScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[0];}),d3.max(dataset, function(d) { return d[0];})])
.range([-1,1]);
var yScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[1];}),d3.max(dataset, function(d) { return d[1];})])
.range([-1,1]);
//This is the function that creates the SVG lines
var line = svg.selectAll("line")
.data(lisa)
.enter()
.append("line");
//This gets the cooresponding x,y cordinates from the dataset
line.attr("x1", function(d) {
return xScale(d[0]);
})
.attr("y1", function(d) {
return yScale(d[1]);
})
.attr("x2", function(d) {
return xScale(d[2]);
})
.attr("y2", function(d) {
return yScale(d[3]);
})
.attr("stroke", "black");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Your code has some problems:
First, your range right now ([-1, 1]) makes no sense. This should be the domain instead (I changed the ranges to [0, w] and [0, h]).
In your real code, the domain should be [-5, 5] and the range should be the limits of the plot, something like [leftLimit, rightLimit] and [topLimit, bottomLimit] (have in mind that, in an SVG, the 0 position for the y axis is the top, not the bottom).
Second, given this array:
["Tucson",-0.663848,0.4111043,-0.6722116,0.011639]
your x and y positions should be the indices 1,2,3 and 4, not 0, 1, 2 and 3.
Besides that changes, I added the labels:
var text = svg.selectAll(".text")
.data(dataset)
.enter()
.append("text");
text.attr("font-size", 10)
.attr("x", function(d) {
return xScale(d[1]);
})
.attr("y", function(d) {
return yScale(d[2]);
})
.text(d => d[0]);
Here is the demo with the corrections:
var dataset = [["Eloy",0.0169808,-0.695317,-0.0510301,-0.6995938],
["Florence",-0.3465685,-0.6790588,-0.5869514,-0.6762134],
["Phoenix",0.677068,-0.5754814,-0.6052215,-0.6158059],
["Tucson",-0.663848,0.4111043,-0.6722116,0.011639]];
var color = d3.scale.category10();
var w = 400;
var h = 300;
//create the svg element and set the height and width parameters
var svg = d3.select("body")
.append("svg")
.attr("height",h)
.attr("width", w)
.style("border", "1px solid black");
//Create the scale for the scatter plot
var xScale = d3.scale.linear()
.domain([-1,1])
.range([0,w]);
var yScale = d3.scale.linear()
.domain([-1,1])
.range([0,h]);
//This is the function that creates the SVG lines
var line = svg.selectAll("line")
.data(dataset)
.enter()
.append("line");
//This gets the cooresponding x,y cordinates from the dataset
line.attr("x1", function(d) {
return xScale(d[1]);
})
.attr("y1", function(d) {
return yScale(d[2]);
})
.attr("x2", function(d) {
return xScale(d[3]);
})
.attr("y2", function(d) {
return yScale(d[4]);
})
.attr("stroke-width", 2)
.attr("stroke", (d,i)=>color(i));
var text = svg.selectAll(".text")
.data(dataset)
.enter()
.append("text");
text.attr("font-size", 10)
.attr("x", function(d) {
return xScale(d[1])+2;
})
.attr("y", function(d) {
return yScale(d[2]) + 4;
})
.text(d=>d[0]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

D3.js: Dragging everything in group ('g') by element contained in the group using origin() function

I am not sure what's going on, but I have 2 very simple examples set up to show what I am asking.
Both examples have a 'g' that contains a 'rect' and 'text'.
In the 1st example, I am setting up drag on the 'g' itself, i.e., if you mousedown anywhere in that group and drag, it will drag the entire thing (both 'rect' and 'text') around the viewpoint.
http://jsfiddle.net/wup4d0nx/
var chart = d3.select("body").append("svg")
.attr("height", 500)
.attr("width", 500)
.style("background", "lightgrey");
var group = chart.selectAll("g")
.data(["Hello"])
.enter()
.append("g")
.attr("id", function (d) { return d;});
var rect = group.append("rect")
.attr("stroke", "red")
.attr("fill", "blue")
.attr("width", 200)
.attr("height", 200)
.attr("x", 10)
.attr("y", 10);
var label = group.append("text")
.attr("x", 40)
.attr("y", 40)
.attr("font-size", "22px")
.attr("text-anchor", "start")
.text(function (d) { return d;});
// Set up dragging for the entire group
var dragMove = function (d) {
var x = d3.event.x;
var y = d3.event.y;
d3.select(this).attr("transform", "translate(" + x + "," + y + ")");
};
var drag = d3.behavior.drag()
.origin(function (data) {
var element = d3.select("#" + data);
return {
x: d3.transform(element.attr("transform")).translate[0],
y: d3.transform(element.attr("transform")).translate[1]
};
})
.on("drag", dragMove);
group.call(drag);
In the 2nd example, which doesn't work and is what I am interested in, I want ONLY THE TEXT to be something the user can grab to drag the entire group around.
I tried many attempts. Some don't work at all, some work but flicker like the example I provide here:
http://jsfiddle.net/9xeo7ehf/
var chart = d3.select("body").append("svg")
.attr("height", 500)
.attr("width", 500)
.style("background", "lightgrey");
var group = chart.selectAll("g")
.data(["Hello"])
.enter()
.append("g")
.attr("id", function (d) { return d;});
var rect = group.append("rect")
.attr("stroke", "red")
.attr("fill", "blue")
.attr("width", 200)
.attr("height", 200)
.attr("x", 10)
.attr("y", 10);
var label = group.append("text")
.attr("x", 40)
.attr("y", 40)
.attr("font-size", "22px")
.attr("text-anchor", "start")
.text(function (d) { return d;});
// Set up dragging for the entire group USING THE LABEL ONLY TO DRAG
var dragMove = function (d) {
var x = d3.event.x;
var y = d3.event.y;
d3.select(this.parentNode).attr("transform", "translate(" + x + "," + y + ")");
};
var drag = d3.behavior.drag()
.origin(function (data) {
var element = d3.select("#" + data);
return {
x: d3.transform(element.attr("transform")).translate[0],
y: d3.transform(element.attr("transform")).translate[1]
};
})
.on("drag", dragMove);
label.call(drag);
What's going on with this that it flickers and what am I doing wrong?
Any help would be greatly appreciated!
Thanks!
I'm not sure exactly why it is flickering (as I am not too familiar with D3), but one way to get it to stop is to use the source event for D3:
// 50 is the offset x/y position you set for your text
var x = d3.event.sourceEvent.pageX - 50;
var y = d3.event.sourceEvent.pageY - 50;
Edit: While the above code works, it causes the box to initially "jump" to the coordinates of the text, A better fix would be to take your first example and just filter our events that aren't executed on the text element. Try putting the following at the top of the dragMove method:
if(d3.event.sourceEvent.target.nodeName !== 'text') {
return;
}
Try d3.event.sourceEvent.stopPropagation(); inside on-drag function

D3 Plot array of circles

I've tried the circle plot example as the following:
var x=20, y=20, r=50;
var sampleSVG = d3.select("#viz")
.append("svg")
.attr("width", 800)
.attr("height", 600);
sampleSVG.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", r)
.attr("cx", x)
.attr("cy", y);
But I want to figure out how to plot without a loop a sequence of circles from an array like:
data = [
[10,20,30],
[20,30,15],
[30,10,25]
];
Maybe this example could help?
var data = [
[10,20,30],
[20,30,15],
[30,10,25]
];
var height = 300,
width = 500;
var svg = d3.select('body').append('svg')
.attr('height', height)
.attr('width', width)
.append('g')
.attr('transform', 'translate(30, 30)');
// Bind each nested array to a group element.
// This will create 3 group elements, each of which will hold 3 circles.
var circleRow = svg.selectAll('.row')
.data(data)
.enter().append('g')
.attr('transform', function(d, i) {
return 'translate(30,' + i * 60 + ')';
});
// For each group element 3 circle elements will be appended.
// This is done by binding each element in a nested array to a
// circle element.
circleRow.selectAll('.circle')
.data(function(d, i) { return data[i]; })
.enter().append('circle')
.attr('r', function(d) { return d; })
.attr('cx', function(d, i) { return i * 60; })
.attr('cy', 0);
Live Fiddle

Categories

Resources