I'm trying to flip a graph in JS without changing the axis.
JavaScript isn't my specialty and after googling for 4 hours I'm starting to think it isn't possible.
Can someone point me in the right direction please?
var width = 960,
height = 500;
d3.json("data2.json", function(error, heatmap) {
var dx = heatmap[0].length,
dy = heatmap.length;
// Fix the aspect ratio.
// var ka = dy / dx, kb = height / width;
// if (ka < kb) height = width * ka;
// else width = height / ka;
var x = d3.scale.linear()
.domain([0, dx])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, dy])
.range([height, 0]);
var color = d3.scale.linear()
.domain([null, 0, 30, 60, 90, 120])
.range(["#FFFFFF", "#FF0000", "#FF9933", "#FFFF00", "#99FF33", "#00FF00"]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("top")
.ticks(20);
var yAxis = d3.svg.axis()
.scale(y)
.orient("right");
d3.select("body").append("canvas")
.attr("width", dx)
.attr("height", dy)
.style("width", width + "px")
.style("height", height + "px")
.call(drawImage);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.call(removeZero);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.call(removeZero);
function drawImage(canvas) {
var context = canvas.node().getContext("2d"),
image = context.createImageData(dx, dy);
for (var y = 0, p = -1; y < dy; ++y) {
for (var x = 0; x < dx; ++x) {
var c = d3.rgb(color(heatmap[y][x]));
image.data[++p] = c.r;
image.data[++p] = c.g;
image.data[++p] = c.b;
image.data[++p] = 255;
}
}
context.putImageData(image, 0, 0);
}
function removeZero(axis) {
axis.selectAll("g").filter(function(d) { return !d; }).remove();
}
});
I see that you're actually not really using D3 for the part of the code that makes the image itself. In the nested loops, when you're manually producing the bitmap image, you can just traverse the data in reverse. Instead of indexing the row heatmap[y] directly on x, you should index on dx - x - 1, the column number counting from the right.
jsfiddle
As an aside, it seems a little strange to be mixing SVG and canvas drawing techniques here. Depending on how much data you're showing, it may be valuable to draw the heatmap with SVG as well, which would allow you to use just a single API to draw the chart and would enable interactions as well. Alternatively, you could go for drawing the whole thing on canvas, if a static image is more appropriate, for instance if the scale of data is massive.
Related
I am trying to create a line graph in d3.js but only my axes are appearing; the line doesn't show.
Things that are working:1. My axes are labelled correctly 2. Looking at the elements of the page in Chrome it seems the x and y attributes for the line are 'working' (i.e. the data for coordinates are defined for the line/are not 'NaN' values). I think there must be something wrong with attributes associated with my line (end of the Javascript code).
Are there any reasons this might be happening?
This is what my plot/graph output currently looks like:
Current state of plot
Here is my HTML, Javascript and the data I've used for the plot:
HTML:
<html>
<head>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<div id="merit-order-chart"></div>
</body>
<script type="text/javascript" src="/src.js"></script>
</html>
JAVASCRIPT:
// create a SVG element
let svg2 = d3.select("#merit-order-chart").append("svg");
// sizing parameters
let margin2 = {top: 20, right: 50, bottom: 40, left: 80};
let width2 = 800;
let height2 = 400;
let chartWidth2 = width2 - margin2.left - margin2.right;
let chartHeight2 = height2 - margin2.top - margin2.bottom;
// sizing the SVG
svg2.attr("width", width2 + "px")
.attr("height", height2 + "px");
// creating the x and y scales
let y2 = d3.scaleLinear()
.clamp(true)
.range([chartHeight2, 0]);
let x2 = d3.scaleTime()
.clamp(true)
.range([0, chartWidth2]);
// formatting of the x and y axes
let xAxis2 = d3.axisBottom()
.scale(x2)
.tickFormat(d3.timeFormat("%Y-%m-%d %H:%M:%S"))
.ticks(4);
let yAxis2 = d3.axisLeft()
.scale(y2)
.ticks(8);
// adding a 'group' element for all the things attached to the chart
let chart2 = svg2.append("g")
.attr("transform", `translate(${margin2.left},${margin2.top})`);
// adding the x and y axis elements to the chart group (g)
const xg2 = chart2.append("g")
.classed("x axis", true)
.attr("transform", `translate(0,${chartHeight2})`)
.call(xAxis2);
const yg2 = chart2.append("g")
.classed("y axis", true)
.call(yAxis2);
d3.csv("/price-data.csv", (err, csv) => {
const clean2 = csv.map(d2 => {
// clean up number formats
d2.p = parseFloat(d2.p);
d2.settlementdate = Date.parse(d2.settlementdate)
d2.index = parseFloat(d2.index);
return d2;
});
// re-sizing the x and y axes
x2.domain([d3.min(clean2, d2 => d2.settlementdate), d3.max(clean2, d2 => d2.settlementdate)]);
xg2.call(xAxis2);
y2.domain([-1000, 14125]);
yg2.call(yAxis2);
chart2.selectAll(".prices")
.data(clean2)
.enter()
.append("line")
.attr("x", d2 => x2(d2.settlementdate))
.attr("y", d2 => y2(d2.p))
.attr("stroke-width", 5)
.attr("stroke", "black")
//.style("stroke", "rgb(6,120,155)");
});
DATA (.csv):
settlementdate,p,index
1/1/2017 0:00,50,1
1/1/2017 0:05,35,2
1/1/2017 0:10,100,3
1/1/2017 0:15,5000,4
You need to use a line generator, currently you are passing an array of objects representing each point, and appending a line for each one - this approach won't work (partly because lines don't have x and y attributes, but x1,x2,y1,y2 attributes).
You need to use a line generator:
let line = d3.line()
.x(function(d) { return x2(d.settlementdate); }) // x value for each point
.y(function(d) { return y2(d.p); }) // y value for each point
This will return a path with one vertex for every coordinate fed to it. Consequently you'll want to append a path rather than a line, and the drawing instructions for a path are contained in the d attribute, so you can use .attr("d", line).
Lastly, since you want one path per dataset, rather than one path per datapoint, nest your data into an array. By doing so you are getting one line with many points, rather than many lines with no points.
I changed the scale to show the curve, but it cuts out the peak as a result:
chart2.selectAll(".prices")
.data([clean2])
.enter()
.append("path")
.attr("d",line)
.attr("stroke-width", 5)
.attr("stroke", "black")
.attr("fill","none")
var csv = [
{ settlementdate: "1/1/2017 0:00",p:50,index:1 },
{ settlementdate: "1/1/2017 0:05",p:35,index:2 },
{ settlementdate: "1/1/2017 0:10",p:100,index:3 },
{ settlementdate: "1/1/2017 0:15",p:5000,index:4 }
]
// create a SVG element
let svg2 = d3.select("#merit-order-chart").append("svg");
// sizing parameters
let margin2 = {top: 20, right: 50, bottom: 40, left: 80};
let width2 = 800;
let height2 = 400;
let chartWidth2 = width2 - margin2.left - margin2.right;
let chartHeight2 = height2 - margin2.top - margin2.bottom;
// sizing the SVG
svg2.attr("width", width2 + "px")
.attr("height", height2 + "px");
// creating the x and y scales
let y2 = d3.scaleLinear()
.clamp(true)
.range([chartHeight2, 0]);
let x2 = d3.scaleTime()
.clamp(true)
.range([0, chartWidth2]);
// formatting of the x and y axes
let xAxis2 = d3.axisBottom()
.scale(x2)
.tickFormat(d3.timeFormat("%Y-%m-%d %H:%M:%S"))
.ticks(4);
let yAxis2 = d3.axisLeft()
.scale(y2)
.ticks(8);
// adding a 'group' element for all the things attached to the chart
let chart2 = svg2.append("g")
.attr("transform", `translate(${margin2.left},${margin2.top})`);
// adding the x and y axis elements to the chart group (g)
const xg2 = chart2.append("g")
.classed("x axis", true)
.attr("transform", `translate(0,${chartHeight2})`)
.call(xAxis2);
const yg2 = chart2.append("g")
.classed("y axis", true)
.call(yAxis2);
let line = d3.line()
.x(function(d) { return x2(d.settlementdate); })
.y(function(d) { return y2(d.p); })
const clean2 = csv.map(d2 => {
// clean up number formats
d2.p = parseFloat(d2.p);
d2.settlementdate = Date.parse(d2.settlementdate)
d2.index = parseFloat(d2.index);
return d2;
});
// re-sizing the x and y axes
x2.domain([d3.min(clean2, d2 => d2.settlementdate), d3.max(clean2, d2 => d2.settlementdate)]);
xg2.call(xAxis2);
y2.domain([0, 200]);
yg2.call(yAxis2);
chart2.selectAll(".prices")
.data([clean2])
.enter()
.append("path")
.attr("d",line)
.attr("stroke-width", 5)
.attr("stroke", "black")
.attr("fill","none")
//.style("stroke", "rgb(6,120,155)");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
<div id="merit-order-chart"></div>
I'm drawing a bar chart with axes, and yScale is behaving differently on my yAxis than on my appended bars.
I set my yScale range to start at (h - yPadding) to leave extra room at the bottom for xAxis labels.
var yScale = d3.scale.linear()
.domain([0, d3.max(val)])
.range([h - yPadding, 0]);
-- The range is inverted, otherwise my yAxis labels are upside down.
When I call the yAxis using yScale, it obeys the starting point of (h - yPadding) and leaves room at the bottom.
But all the "rects" I'm appending to the chart, start at h, instead of (h - yPadding) even though I'm calling yScale on these "rects" just like on yAxis.
If I change the range to [h, 0] instead of [h - yPadding, 0], only the yAxis reacts to the change, and the bars still start at h.
Why are the bars ignoring the yScale?
<script type="text/javascript">
var xhr = new XMLHttpRequest();
function makeRequest(){
xhr.open("GET", "https://api.coinmarketcap.com/v1/ticker/", true);
xhr.send();
xhr.onreadystatechange = processRequest;
}
function processRequest(){
console.log("testing, state: ", xhr.readyState)
if(xhr.readyState == 4 && xhr.status == 200){
dataset = [];
for(var i = 0; i < 10; i++){
addingId = JSON.parse(xhr.responseText)[i];
addingId.id = i;
dataset.push(addingId);
}
console.log("this is dataset: ", dataset);
makeChart();
}
}
function makeChart(){
var w = 1000;
var h = 600;
var padding = 40;
var yPadding = 80;
var val = [];
dataset.forEach(function(ele){
val.push(parseInt(ele.market_cap_usd));
})
var max = d3.max(val)
var xAxisNames = []
dataset.forEach(function(ele){ xAxisNames.push(ele.name); })
// console.log(">>>>>>>>", xAxisNames)
var xScale = d3.scale.ordinal()
.domain(d3.range(dataset.length))
.rangeRoundBands([padding, w - padding], 0.05)
var yScale = d3.scale.linear()
.domain([0, d3.max(val)])
.range([h - yPadding, 0]);
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.tickFormat(function(d){
if(d > 0){ return d / 1000000000 + " b"; }
return "";
})
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickFormat(function(d, i){
return xAxisNames[i]
})
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i){
return xScale(i);
})
.attr("y", function(d){
return yScale(d.market_cap_usd);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d, i){
return h - yScale(d.market_cap_usd)
})
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + padding + ", 0)")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0, " + (h - yPadding) + ")")
.call(xAxis)
.selectAll("text")
.attr("y", 15)
.attr("font-size", 12)
.attr("x", xScale.rangeBand() / 2)
.attr("transform", "rotate(45)")
}
makeRequest();
</script>
A scale just maps an input domain to an output range, nothing more. You have to set the positions and the dimensions of the SVG elements accordingly. Let's see:
Right now, given your scale, when you pass it the minimum value in your domain it will return:
h - yPadding
You want such bars having a height of zero pixels, of course. To get that zero the equation is simple, you have to subtract from that value:
(h - yPadding) - yScale(minimumDomainValue)
That will give you zero for the minimum value in the domain.
Therefore, this should be the height of the rectangles:
.attr("height", function(d, i){
return (h - yPadding) - yScale(d.market_cap_usd)
})
PS: by the way, in D3, one of the few situations where a scale determines the dimensions of a SVG element is the path/lines created by an axis generator. That's why you're seeing a different behaviour in your axis.
I am having an issue with d3.js when I try to zoom in and out on a graph. The zoom is very slow and laggy. I am trying to debug by using the profiling tool (Opera/Chrome). I was expecting my zoom callback function to be the limiting factor but it turns out there is a lot of idle time between each mousewheel scroll events.
Motus operandum: I start the profiling, then give a big sharp scroll on the mousewheel (5sec on the graph). The graph lags for several seconds(from 5sec to 8.5sec on the graph) then calls my zoom callback periodically (from 8.5 to 14sec on the graph). I checked the stack calls and all my zooming callbacks are executed in order, synchronously, which makes me think the are done executing during the idle time. I think the profiler does not record some of the system/browser calls and qualifies those as idle, so I tried using interruptions ( event.preventDefault() etc...) to make sure nothing was executed on zoomend. It improved a little bit the performance, but there is still a lot of idle time:
Can someone please help me figure out why there is so much idle time?
Here is my relevant code:
without interruption
d3Zoom = d3.behavior.zoom()
.x(element.self.xScale)
.y(element.self.yScale)
.scaleExtent([0.99, Infinity])
.on("zoom", semanticZoom)
.on("zoomend", updateSelection);
with interruption
var delayTimer=0;
d3Zoom = d3.behavior.zoom()
.x(xScale)
.y(yScale)
.scaleExtent([0.99, Infinity])
.on("zoom", semanticZoom)
.on("zoomstart", function () {
//prevent recalculating heavyCalculations too often
window.clearTimeout(delayTimer);
var evt = e ? e : window.event;
return cancelDefaultAction(evt);
})
.on("zoomend", function () {
// only start heavy calculations if user hasn't zoomed for 0.75sec
delayTimer = window.setTimeout(updateSelection, 750);
});
function cancelDefaultAction(e) {
var evt = e ? e : window.event;
if (evt.preventDefault) evt.preventDefault();
evt.returnValue = false;
return false;
}`
EDIT: Here is an example of working code. Both semanticZoom and update selection are more complex in my project than in this example but they involve custom AngularJS directives, d3 brushes, warped geometry, aggregation etc... I have cropped semanticZoom to just perform an enter/exit/update pattern based on a quadtree (it might behave funny in this the example, but it's just to show the kind of operations I do). UpdateSelection updates the visible data to an angular directive to perform calculations (various statistics etc...). I did not populate it here but it is not actually very intensive.
var size = 100;
var dataset = d3.range(10).map(function(d, idx) {
return {
x: d3.random.normal(size / 2, size / 4)(),
y: d3.random.normal(size / 2, size / 4)(),
uuid: idx
};
});
//
// Init Scales
//
var xScale = d3.scale.linear()
.domain([0, size])
.range([0, 100]);
var yScale = d3.scale.linear()
.domain([0, size])
.range([0, 100]);
//
// Init Axes
//
var xAxis = d3.svg.axis()
.scale(xScale)
.ticks(10)
.orient("bottom")
.tickSize(-size);
var yAxis = d3.svg.axis()
.scale(yScale)
.ticks(10)
.orient("left")
.tickSize(-size);
//
// Init Zoom
//
var d3Zoom = d3.behavior.zoom()
.x(xScale)
.y(yScale)
.scaleExtent([0.99, Infinity])
.on("zoom", semanticZoom)
.on("zoomend", updateSelection);
var quadtree = d3.geom.quadtree(dataset);
//------------------------ Callbacks --------------------------------
function semanticZoom() {
var s = 1;
var t = [0, 0];
if (d3.event) {
s = (d3.event.scale) ? d3.event.scale : 1;
t = (d3.event.translate) ? d3.event.translate : [0, 0];
}
// set zoom boundaries
// center of the zoom in svg coordinates
var center = [(size / 2 - t[0]) / s, (size / 2 - t[1]) / s];
// half size of the window in svg coordinates
var halfsize = size / (2 * s);
// top left corner in svg coordinates
var tl = [center[0] - halfsize, center[1] - halfsize];
// bottom right corner in svg coordinates
var br = [center[0] + halfsize, center[1] + halfsize];
/*
//
// Constrain zoom
//
if (!(tl[0] > -10 &&
tl[1] > -10 &&
br[0] < size + 10 &&
br[1] < size + 10)) {
// limit zoom-window corners
tl = [Math.max(0, tl[0]), Math.max(0, tl[1])];
br = [Math.min(size, br[0]), Math.min(size, br[1])];
// get restrained center
center = [(tl[0] + br[0]) / 2, (tl[1] + br[1]) / 2];
// scale center
t = [size / 2 - s * center[0], size / 2 - s * center[1]];
// update svg
svg.transition()
.duration(1)
.call( d3Zoom.translate(t).event );
}
*/
//
// Store zoom extent
//
d3Zoom.extent = [tl, br];
d3Zoom.scaleFactor = s;
d3Zoom.translation = t;
//
// Update some heavy duty stuff
// (create a quadtree, search that quadtree and update an attribute for the elements found)
//
// Prune non visible data
var displayedData = search(quadtree,
d3Zoom.extent[0][0], d3Zoom.extent[0][1],
d3Zoom.extent[1][0], d3Zoom.extent[1][1]);
redrawSubset(displayedData);
//
// Update axes
//
d3.select(".x.axis").call(xAxis);
d3.select(".y.axis").call(yAxis);
}
function redrawSubset(subset) {
//Attach new data
var elements = d3.select(".data_container")
.selectAll(".datum")
.data(subset, function(d) {
return d.uuid;
});
//enter
elements.enter()
.append("circle")
.attr("class", "datum")
.attr("r", 1)
.style("fill", "black");
//exit
elements.exit().remove();
//update
elements.attr("transform", ScaleData);
}
function updateSelection() {
// some not so heavy duty stuff
}
function ScaleData(d) {
return "translate(" + [xScale(d.x), yScale(d.y)] + ")";
}
//
// search quadtree
//
function search(qt, x0, y0, x3, y3) {
var pts = [];
qt.visit(function(node, x1, y1, x2, y2) {
var p = node.point;
if ((p) && (p.x >= x0) && (p.x <= x3) && (p.y >= y0) && (p.y <= y3)) {
pts.push(p);
}
return x1 >= x3 || y1 >= y3 || x2 < x0 || y2 < y0;
});
return pts;
}
//------------------------- DOM Manipulation -------------------------
var svg = d3.select("body").append("svg")
.attr("width", size)
.attr("height", size)
.append("g")
.attr("class", "data_container")
.call(d3Zoom);
svg.append("rect")
.attr("class", "overlay")
.attr("width", size)
.attr("height", size)
.style("fill", "none")
.style("pointer-events", "all");
var circle = svg.selectAll("circle")
.data(dataset, function(d) {
return d.uuid;
}).enter()
.append("circle")
.attr("r", 1)
.attr("class", "datum")
.attr("transform", ScaleData);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
SemanticZoom and UpdateSelection have both been unit tested and run in times comparable to the profiler graphs above (50-100ms) for large datasets.
If you add a few zeros to the circle count and make the svg big enough to be useful, then the zoom slows down to what you describe. But it's hardly surprising since it has a bunch of work to do visiting the nodes in the quad tree and writing to the DOM to manage the svg components. I don't understand why you are transforming individual circles instead of grouping them and transforming the g. If you did that then you could just let the svg element clip the image and avoid all of the svg overheads which would free up 75% of your budget. If the only purpose of the quad tree is to figure out which nodes are visible then that would also be eliminated.
A key observation I guess is that this profile is markedly different from the pics you posted, judging by the profile of your pics, they seem to be all about the quad tree and the rest is idle time. It would be interesting to see your cpu and gpu loading during the profile.
You can eliminate the need for deleting and re-writing nodes by using a clip path, that way the only overhead is re-writing the transform attributes.
There was also a problem with your search. There is a much simpler way to do it that works fine and that is to use the #linear.invert(y) method of the scale.
Both these are addressed in the sample code below...
var size = 500;
var margin = {top: 30, right: 40, bottom: 30, left: 50},
width = 600 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
d3.select("#clipButton").on("click", (function() {
var clipped = false, clipAttr = [null, "url(#clip)"],
value = ["clip", "brush"];
return function() {
circles
.attr("clip-path", clipAttr[(clipped = !clipped, +clipped)]);
this.value = value[+clipped];
}
})());
var dataset = d3.range(1000).map(function(d, idx) {
return {
x: d3.random.normal(100 / 2, 100 / 4)(),
y: d3.random.normal(100 / 2, 100 / 4)(),
uuid: idx
};
});
//
// Init Scales
//
var xScale = d3.scale.linear()
.domain([0, 100])
.range([0, width])
.nice(10);
var yScale = d3.scale.linear()
.domain([0, 100])
.range([height, 0])
.nice(10);
//
// Init Axes
//
var xAxis = d3.svg.axis()
.scale(xScale)
.ticks(10)
.orient("bottom")
.tickSize(-height);
var yAxis = d3.svg.axis()
.scale(yScale)
.ticks(10)
.orient("left")
.tickSize(-width);
//
// Init Zoom
//
var d3Zoom = d3.behavior.zoom()
.x(xScale)
.y(yScale)
.scaleExtent([0.99, Infinity])
.on("zoom", semanticZoom)
// .on("zoomend", updateSelection);
var Quadtree = d3.geom.quadtree()
.x(function(d){return d.x})
.y(function(d){return d.y});
quadtree = Quadtree(dataset);
//------------------------ Callbacks --------------------------------
function semanticZoom() {
var s = 1;
var t = [0, 0];
if (d3.event) {
s = (d3.event.scale) ? d3.event.scale : 1;
t = (d3.event.translate) ? d3.event.translate : [0, 0];
}
var tl = [xScale.invert(0), yScale.invert(height)];
var br = [xScale.invert(width), yScale.invert(0)];
//
// Store zoom extent
//
d3Zoom.extent = [tl, br];
d3Zoom.scaleFactor = s;
d3Zoom.translation = t;
//
// Update some heavy duty stuff
// (create a quadtree, search that quadtree and update an attribute for the elements found)
//
// Prune non visible data
var displayedData = search(quadtree, d3Zoom.extent);
markSubset(displayedData, circle);
updateSelection(circle);
//
// Update axes
//
d3.select(".x.axis").call(xAxis);
d3.select(".y.axis").call(yAxis);
};
function markSubset(data, nodes){
var marked = nodes.data(data, function(d){return d.uuid;});
marked.enter();
marked.classed("visible", true);
marked.exit().classed("visible", false);
}
function updateSelection(elements) {
// some not so heavy duty stuff
elements.attr("transform", ScaleData);
}
function ScaleData(d) {
return "translate(" + [xScale(d.x), yScale(d.y)] + ")";
}
//
// search quadtree
//
function search(qt, extent) {
var pts = [],
x0=extent[0][0], y0=extent[0][1],
x3=extent[1][0], y3=extent[1][1];
qt.visit(function(node, x1, y1, x2, y2) {
var p = node.point;
if ((p) && (p.x >= x0) && (p.x <= x3) && (p.y >= y0) && (p.y <= y3)) {
pts.push(p);
}
return x1 >= x3 || y1 >= y3 || x2 < x0 || y2 < y0;
});
return pts;
}
//------------------------- DOM Manipulation -------------------------
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("class", "data_container")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(d3Zoom),
plotSurface = svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.style({"fill": "steelblue", opacity: 0.8})
.style("pointer-events", "all"),
gX = svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis),
gY = svg.append("g")
.attr("class", "y axis")
.call(yAxis),
clipRect = svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height),
circles = svg.append("g")/*
.attr("clip-path", "url(#clip)")*/,
circle = circles.selectAll("circle")
.data(dataset, function(d) {
return d.uuid;
});
circle.enter()
.append("circle")
.attr("r", 3)
.attr("class", "datum")
.attr("transform", ScaleData);
semanticZoom();
svg {
outline: 1px solid red;
overflow: visible;
}
.axis path {
stroke: #000;
}
.axis line {
stroke: steelblue;
stroke-opacity: .5;
}
.axis path {
fill: none;
}
.axis text {
font-size: 8px;
}
.datum {
fill: #ccc;
}
.datum.visible {
fill: black;
}
#clipButton {
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<input id="clipButton" type="button" value="clip">
somehow I cannot turn off smoothing on my image drawn on my canvas using d3.js. I tried to disable imageSmoothingEnabled in my context. What is my mistake?
Thank you very much for any help solving my issue.
Thomas
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
var width = 500,
height = 200;
heatmap=
[
[200,104,104,105, 80,106,106,106,107,170],
[104,104,105,105,106,106,107,107,107,107],
[104,105,105,106,106,107,107,128,138,148]
];
var dx = heatmap[0].length,
dy = heatmap.length;
var x = d3.scale.linear()
.domain([0, dx])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, dy])
.range([height, 0]);
var color = d3.scale.linear()
.domain([5, 115, 135, 155, 175, 295])
.range(["#0a0", "#6c0", "#ee0", "#eb4", "#eb9", "#fff"]);
d3.select("body").append("canvas")
.attr("width", dx)
.attr("height", dy)
.style("width", width + "px")
.style("height", height + "px")
.call(drawImage);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.call(removeZero);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.call(removeZero);
function drawImage(canvas) {
var context = canvas.node().getContext("2d");
context.imageSmoothingEnabled = false; //seems to not fire
context.mozImageSmoothingEnabled = false;
context.webkitImageSmoothingEnabled = false;
context.msImageSmoothingEnabled = false;
var image = context.createImageData(dx, dy);
for (var y = 0, p = -1; y < dy; ++y) {
for (var x = 0; x < dx; ++x) {
var c = d3.rgb(color(heatmap[y][x]));
image.data[++p] = c.r;
image.data[++p] = c.g;
image.data[++p] = c.b;
image.data[++p] = 255;
}
}
context.putImageData(image, 0, 0);
}
</script>
Using D3 I want to create an X Axis that looks like:
I've worked out how to do the axis and ticks, but not the labels using the following:
var svgWidth = 500;
var svgHeight = 500;
var svgAxisPadding = 20;
var xScale = d3.scale.log()
.domain([Math.pow(10, 5), Math.pow(10, 7)])
.range([svgAxisPadding, svgWidth - svgAxisPadding]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.ticks(0, "e");
var svg = d3.select('#diagram')
.append('svg')
.attr('width', svgWidth)
.attr('height', svgHeight);
svg.append('g')
.attr("class", "axis")
.call(xAxis);
And here's a jsFiddle with the complete code.
You could use unicode:
var superscript = "⁰¹²³⁴⁵⁶⁷⁸⁹",
formatPower = function(d) { return (d + "").split("").map(function(c) { return superscript[c]; }).join(""); },
formatTick = function(d) { return 10 + formatPower(Math.round(Math.log(d) / Math.LN10)); };
For example, formatTick(1e5) returns "10⁵". Example at bl.ocks.org/6738109:
The downside of this approach is that the vertical alignment of the superscript numerals seems inconsistent. So using post-selection (say, selecting the text elements and adding a tspan element for the superscript to each) might be better. Another example at bl.ocks.org/6738229:
There's a tickFormat function available on the axis. Unfortunately, it expects a String as a return value and plops that on the axis. This would be great if you wanted to display 10^6, but not as helpful when you want to use the superscript notation.
A workaround is to create 2 axes: one for displaying the 10 and another for displaying the exponent. Here's an example:
var svgWidth = 500;
var svgHeight = 500;
var svgAxisPadding = 20;
var xScale = d3.scale.log()
.domain([Math.pow(10, 5), Math.pow(10, 7)])
.range([svgAxisPadding, svgWidth - svgAxisPadding]);
var xAxis = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.ticks(0, "e")
.tickFormat(function (d) {
var log = Math.log(d) / Math.LN10;
return Math.abs(Math.round(log) - log) < 1e-6 ? 10 : '';
});
var xAxis2 = d3.svg.axis()
.scale(xScale)
.orient('bottom')
.ticks(0, "e")
.tickFormat(function (d) {
var log = Math.log(d) / Math.LN10;
return Math.abs(Math.round(log) - log) < 1e-6 ? Math.round(log) : '';
});
var svg = d3.select('#diagram')
.append('svg')
.attr('width', svgWidth)
.attr('height', svgHeight);
svg.append('g')
.attr("class", "axis")
.call(xAxis);
svg.append('g')
.attr("class", "axis")
.attr("transform", "translate(12, -5)") //shifted up and to the right
.style("font-size", "12px")
.call(xAxis2);
It's not necessarily the most elegant solution, but it works.