I'm using D3 (v3) to build a plot with two axis. One of them it isn't working correctly when I add two or more objects. My piece of code is:
//Set upperLevelyScale
var previousValues = 0;
var upperLevelyScale = d3.scale.ordinal()
.domain(Object.keys(valuesY_numbers).sort())
.rangePoints((function(){
var values = Object.values(valuesY_numbers).map(function(x){
previousValues += (x * itemSize);
return previousValues});
values.unshift(0);
return values;
})());
when domain is ["MyValue1"] and rangePoints is [0, 170], the tick of the axis shows perfectly at the middle of the axis. But if domain is ["MyValue1", "MyValue2"] and rangePoints is [0,170,320] the ticks are not really covering their part but other.
What I'm trying to set up is just: MyValue1 is from 0 to 170, MyValue2 is from 170 to 320, etc. I have tried with '.range' also but still not working.
Thanks in advance.
Related
I am trying to recreate this visualization using p5.js. I have some trouble understanding how to create the coordinates for the new points and plot them on my canvas.
The data is a series of negative-positive values that need to be plotted below and above an X-axis respectively (from left to right). This is a sample:
"character","roll_value"
"Daphne Blake",0
"Daphne Blake",-1
"Daphne Blake",-1
"Daphne Blake",-5
"Daphne Blake",-3
"Daphne Blake",2
So I know that I have to map the values between a certain negative and positive height so I've demarcated those heights as follows:
let maxNegativeHeight = sketch.height - 120;
let maxPositiveHeight = sketch.height/4;
For mapping the input I thought of creating a new function called mapToGraph which takes in the roll_value, the old X position, max height and min height. This would map the old values to a new incremented X position and a vertical height:
const mapToGraph = (value, oldXPos, maxHeight, minHeight) => {
const newXPos = oldXPos + 10;
const newYPos = sketch.map(value, 0, maxHeight, minHeight, maxHeight);
return [newXPos, newYPos];
};
In my draw function, I am drawing the points as follows:
sketch.draw = () => {
for(let i = 0; i < data.getRowCount(); i++) {
let character = data.getString(i, "character");
if(character === 'Daphne Blake'){
console.log(character);
// Draw a horizontal line in the middle of the canvas
sketch.stroke('#F18F01');
sketch.line(0, sketch.height/2, sketch.width, sketch.height/2);
// Plot the data points
let value = data.getNum(i, "roll_value");
let [newX, newY] = mapToGraph(value, 0, maxNegativeHeight, maxPositiveHeight);
console.log(newX, newY);
sketch.strokeWeight(0.5);
sketch.point(newX, newY);
}
}
};
However, this does not plot any points. My console.log shows me that I am not processing the numbers correctly, since all of them look like this:
10 -3
cardThree.js:46 Daphne Blake
cardThree.js:55 10 -4
cardThree.js:46 Daphne Blake
cardThree.js:55 10 -4
cardThree.js:46 Daphne Blake
What am I doing wrong? How can I fix this and plot the points like the visualization I linked above?
Here is the full code of what I've tried (live link to editor sketch).
This is the full data
In your code newX is always 10 since you always pass 0 as the second argument to mapToGraph. Additionally the vertical displacement is always very small and often negative. Since you are using newY directly rather than relative to the middle of the screen many of the points are off screen.
I'm implementing a 2d chart using canvas. I want to reuse d3's logic for generating the chart's axes. d3 does quite a lot of good work in generating axes and I'd like to take advantage of it.
(Note: For backward-compatibility reasons I'm stuck using d3v3 for the time being.)
Consider the following d3 code:
let scale = d3.time.scale()
.range([0, width])
.domain([lo, hi]);
let axis = d3.svg.axis()
.scale(scale)
.ticks(num_ticks)
.tickSize(10)
.orient("bottom");
I can render this into a chart div with:
svg.selectAll('.x-axis').call(axis);
I want to be able to programmatically get the tick data out of axis, including the formatted labels, by writing a function that behaves as follows:
ticks = get_axis_ticks(axis)
ticks should hold each tick position (as a Date in this particular case) and the corresponding d3-generated label.
[[Date(...), "Wed 19"],
[Date(...), "Fri 21"],
[Date(...), "Apr 23"],
[Date(...), "Tue 25"],
...]
I could then use this data to paint an axis on my canvas.
I've dug into d3v3 source (in particular here: https://github.com/d3/d3/blob/v3.5.17/src/svg/axis.js) but I find it very difficult to tease apart the logic from the SVG manipulation.
Help would be much appreciated.
One idea I have is to use the scale function you have created to generate the ticks you desire and push them into an array.
As a very simple example, if you would like 10 ticks, each incrementing by a unit of 1, you could do something like this: https://jsfiddle.net/Q5Jag/3148/
//define dummy values
var lo = 1;
var hi = 10;
var width = 512
var scale = d3.time.scale()
.range([0, width])
.domain([lo, hi]);
//define your function
var get_x_axis = function() {
let axisArr = [];
for(var i = 0; i < 10; i++ ) {
//calculate your value
axisArr.push(scale(i))
}
return axisArr
}
//call it
let axisTicks = get_x_axis()
//log it
console.log(axisTicks)
I'm not sure if this is what you're looking for, but if you need further help just ask.
I was able to get this working. I found the time formatting strategy in the d3 docs: https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Formatting.md#format_multi I believe this is the strategy that d3 itself uses by default when users do not provide a custom format.
I learned that simply calling scale.ticks(N) will return Nish ticks suitable for rendering on an axis. These tick values are chosen on natural boundaries. E.g., if you're working with a time axis, these ticks will be aligned on minute, hour, day boundaries.
Here's a solution:
let time_format = d3.time.format.multi([
[".%L", d => d.getMilliseconds()],
[":%S", d => d.getSeconds()],
["%I:%M", d => d.getMinutes()],
["%I %p", d => d.getHours()],
["%a %d", d => d.getDay() && d.getDate() !== 1],
["%b %d", d => d.getDate() !== 1],
["%B", d => d.getMonth()],
["%Y", () => true]
]);
let get_ticks = (width, lo, hi, num_ticks) => {
let scale = d3.time.scale().range([0, width]).domain([lo, hi]);
return scale.ticks(num_ticks).map(t => [t, time_format(t)]);
};
I am trying to do exactly what is in this official HighCharts fiddle: http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/members/renderer-label-on-chart/
However the example's "label" function has hardcoded the x(270) and y(50) parameters:
function (chart) { // on complete
var point = chart.series[0].points[8];
chart.renderer.label('Max observation', 270, 50, 'callout',
point.plotX + chart.plotLeft, point.plotY + chart.plotTop)
My chart obviously will require different parameters. I tried using point's plotX etc. However these are undocumented. in fact the are are not part of API as a (presumably) HighCharts developer points out in another answer - they are just inner properties to get coordinates where plot point. in other word, undocumented.
Using them is shorthand for getting values from point:
http://api.highcharts.com/highcharts#Point.x
http://api.highcharts.com/highcharts#Point.y
And translating to position via:
http://api.highcharts.com/highcharts#Axis.toPixels()
The links above seem completely unrelated.
I tried this to divine what those coordinates provide
}, function (chart) { // on complete
var point = chart.series[0].points[8];
chart.renderer.label('.'
, point.plotX.toFixed(0), point.plotY.toFixed(0), 'callout', point.plotX + chart.plotLeft, point.plotY + chart.plotTop)
.add();
});
seems plotX is some point situated a random set of pixels to the left of the chart series point that provides it (about 60ish) and seems to depend on the font you use.
This seems to be what you're looking for:
var point = chart.series[0].points[8];
var pxX = chart.xAxis[0].toPixels(point.x, true);
var pxY = chart.yAxis[0].toPixels(point.y, true);
chart.renderer.label('Max observation', pxX, pxY, 'callout', point.plotX + chart.plotLeft, point.plotY + chart.plotTop);
Fiddle - Notice .toPixels works per Axis, so you need to determine the pixel representation of point X and point Y separately. The true parameter to the function positions the callout based on its point, rather than the top left corner of the callout.
I am trying to plot a trajectory in real-time using Javascript and Highcharts. The data will come from external sensors but for the moment I was practicing with this example:
http://jsfiddle.net/0fp1gzw8/1/
As you can see, the JS snippet tries to plot a circle using a cosine and a sine function:
load: function () {
var series = this.series[0];
setInterval(function () {
a = a + 0.1;
x = Math.sin(a),
y = Math.cos(a);
series.addPoint([x, y], true);
}, 100);
}
The problem is that once the point has crossed the x axes, the line segment is no more drawn between two consecutive samples, instead it connects the new sample with one of the old ones already plotted before:
How can I solve this and get a clean x-y plot?
Thanks
Highcharts expects spline/line chart data to always be sorted by the x value. With this expectation, when you call addPoint it looks like it draws the line segment to the previous x-value not the previously added point.
If you switch your code to use setData:
var data = [];
var series = this.series[0];
setInterval(function () {
a = a + 0.1;
x = Math.sin(a),
y = Math.cos(a);
data.push([x,y]);
series.setData(data, true);
}, 100);
it draws the line segments correctly but you get lots of these errors in the console:
Highcharts error #15: www.highcharts.com/errors/15
You might have better luck switching to a scatter chart that doesn't have this limitation. If you need the line segments, you could add them yourself with the Renderer.
not sure if I am going about this the right way but here goes...
So i have the this example see fiddle here
using lineplusbarchart and i am building on it from this question i posted here:
SO question
I have edited the lineplusbarchart to show the labels on the xaxis:
chart.xAxis.tickFormat(function(d) {
var dx = testdata[0].values[d] && testdata[0].values[d].x || 0;
return dx;
})
.showMaxMin(false);
but i am still having a couple of issues to get what i want...
1 -> how can i make the y1 and y2 axis be alligned? (ideally it would be good if there was only one axis)
2 -> how do i remove the y2 axis? (soution here but this does not work as I then want the 2 axis aligned)
3 -> how do i make the thickness of the barchart part for label1 and label5 to be the same thickness as the others(lable2,3 and 4)?
hope this helps:
you can use chart.lines.forceY() to set a range. To make it
work with dynamic values I'd suggest to find the overall max value of the attached data
and use it for the bar and the lines. Eg:
var maxValue = d3.max(d3.entries(testdata), function(d) {
return d3.max(d3.entries(d.value.values), function(e) {
return e.value.y;
});
}),
minValue = 0;
chart.bars.forceY([minValue, maxValue]);
chart.lines.forceY([minValue, maxValue]);
Your posted solution is exactly what I would do too.
Remove padData()