I have the following bubble chart coded with dc.js which is built upon d3.js.
All is good, but for some reason I cannot see the tick marks. When I inspect the DOM I can see that they are present:
<line y2="6" x2="0"></line>
And I have applied CSS styles to them, but still they do not show!
#referrals-bubble-chart .axis .tick line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
I even added a stroke-width of 2px and still nothing shows! I know I'm targeting the right elements in my CSS because when I give it a stroke width of 10px and hover (Chrome Inspector), I see that the line is now 10px wide.
Why is this happening? Chart Code is below:
// define the referrals bubble chart attributes
referralsChart
.width(700)
.height(400)
.transitionDuration(1500) // (optional) define chart transition duration, :default = 750
.margins({top: 10, right: 50, bottom: 40, left: 50})
.dimension(diagnosisDimension)
//Bubble chart expect the groups are reduced to multiple values which would then be used
//to generate x, y, and radius for each key (bubble) in the group
.group(diagnosisDimensionGroup)
.colors(colorbrewer.RdYlGn[9]) // (optional) define color function or array for bubbles
.colorDomain([0, 100]) //(optional) define color domain to match your data domain if you want to bind data or color
.colorAccessor(function (d) {
// color - mapped to internal scale
return d.value.cost % 100;
})
.keyAccessor(function (p) {
// x-axis
return p.value.avgrtt / p.value.referrals;
})
.valueAccessor(function (p) {
// y-axis
return p.value.cost / 1000;
})
.radiusValueAccessor(function (p) {
// radius size - default is [0, 100]
return p.value.referrals;
})
.maxBubbleRelativeSize(0.1)
// .x(d3.scale.linear().domain([0, 5000]))
.x(d3.scale.linear().domain([1, 15]))
.y(d3.scale.linear().domain([1000, 10000]))
.r(d3.scale.linear().domain([0, 4000]))
//##### Elastic Scaling
//`.elasticX` and `.elasticX` determine whether the chart should rescale each axis to fit data.
//The `.yAxisPadding` and `.xAxisPadding` add padding to data above and below their max values in the same unit domains as the Accessors.
.elasticY(true)
.elasticX(false)
.yAxisPadding(200)
.xAxisLabel('Average Waiting Time - (weeks)') // (optional) render an axis label below the x axis
.yAxisLabel('Cost - (£1K)') // (optional) render a vertical axis lable left of the y axis
//#### Labels and Titles
//Labels are displaed on the chart for each bubble. Titles displayed on mouseover.
.renderLabel(true) // (optional) whether chart should render labels, :default = true
.label(function (p) {
return p.key;
})
.renderTitle(true) // (optional) whether chart should render titles, :default = false
.title(function (p) {
return [p.key,
"Referrals: " + p.value.referrals,
"Cost: £" + p.value.cost,
"RTT: " + p.value.avgrtt / p.value.referrals + " weeks"]
.join("\n");
})
//#### Customize Axis
//Set a custom tick format. Note `.yAxis()` returns an axis object, so any additional method chaining applies to the axis, not the chart.
.yAxis().tickFormat(function (v) {
return v;
});
As mentioned in the comments, it's hard to help you without a complete example, but this works for me. Since I don't have your data I made my own very simple data and adjusted a few things on my bubble chart.
var data = [];
for (var i = 1; i < 10; i++) {
data.push({
val: i
});
}
var ndx = crossfilter(data);
var dim = ndx.dimension(function(d) {
return d.val;
});
var group = dim.group().reduceSum(function(d) {
return d.val;
});
bubbleChart = dc.bubbleChart("#bubbleChart");
bubbleChart
.width(700)
.height(400)
.transitionDuration(1500)
.margins({top: 10, right: 50, bottom: 40, left: 50})
.dimension(dim)
.group(group)
.keyAccessor(function (p) {
return p.value;
})
.valueAccessor(function (p) {
return p.value;
})
.maxBubbleRelativeSize(0.1)
.x(d3.scale.linear().domain([-1, 10]))
.y(d3.scale.linear().domain([0, 10]))
.radiusValueAccessor(function (p) {
return p.value;
})
.r(d3.scale.linear().domain([0, 100]))
.elasticY(true)
.elasticX(false)
.yAxisPadding(200)
.xAxisLabel('Average Waiting Time - (weeks)')
.yAxisLabel('Cost - (£1K)')
.renderLabel(true)
.label(function (p) {
return p.key;
})
.renderTitle(true)
.title(function (p) {
return "This is the title";
})
.yAxis().tickFormat(function (v) {
return v;
});
dc.renderAll();
Related
I'm working on a highcharts solution which includes several different graph types in one single chart. Is it possible to have the exact time of mouse position displayed in the tooltip instead of a calculated range? (We're using highstock together with the boost and xrange module)
Also i need to always show the newest value of a series left from cursor position. Due to having an xRange Series i needed to refresh the tooltip with my own implementation since stickytracking doesnt work with xrange.
But right now the display of the green temperature series constantly switches from the actual value to the first value in the series (e.g when hovering around March 11th it constantly changes from 19.85°C back to 19.68°C which is the very first entry)
So i'm having 2 issues:
displaying the exact time in tooltip
displaying specific values in tooltip
I guess both could be solved with having the exact x position of cursor and for displaying the values i guess i did somewhat the right thing already with refreshing the tooltip on mousemove. Still the values won't always display properly.
I understand that Highcharts makes a best guess on the tooltip time value by displaying the range but to me it seems like it orients itself around the xRange Series.
I already tries to tinker around with the plotoptions.series.stickyTracking and tooltip.snap values but this doesn't really help me at all.
I understand too that this.x in tooltip formatter function will be bound to the closest point. Still i need it to use the current mouse position.
In a first attempt i was filtering through the series in the tooltip formatter itself before i changed to calculating the points on the mousemove event. But there i also couldn't get the right values since x was a rough estimate anyways.
Is there any solution to that?
At the moment i'm using following function onMouseMove to refresh the tooltip:
chart.container.addEventListener('mousemove', function(e) {
const xValue = chart.xAxis[0].toValue(chart.pointer.normalize(e).chartX);
const points = [];
chart.series.filter(s => s.type === "xrange").forEach(s => {
s.points.forEach(p => {
const { x, x2 } = p;
if (xValue >= x && xValue <= x2) points.push(p);
})
})
chart.series.filter(s => s.type !== "xrange").forEach(s => {
const point = s.points.reverse().find(p => p.x <= xValue);
if(point) points.push(point);
})
if (points.length) chart.tooltip.refresh(points, chart.pointer.normalize(e));
})
also i'm using this tooltip configuration and formatter:
tooltip: {
shared: true,
followPointer: true,
backgroundColor: "#FFF",
borderColor: "#AAAAAA",
borderRadius: 5,
shadow: false,
useHTML: true,
formatter: function(){
const header = createHeader(this.x)
return `
<table>
${header}
</table>
`
}
},
const createHeader = x => {
const headerFormat = '%d.%m.%Y, %H:%M Uhr';
const dateWithOffSet = x - new Date(x).getTimezoneOffset() * 60 * 1000;
return `<tr><th colspan="2" style="text-align: left;">${Highcharts.dateFormat(headerFormat, dateWithOffSet)}</th></tr>`
}
See following jsFiddle for my current state (just remove formatter function to see the second issue in action): jsFiddle
(including the boost module throws a script error in jsFiddle. Don't know if this is important so i disabled it for now)
finally found a solution to have access to mouse position in my tooltip:
extending Highcharts with a module (kudos to Torstein Hønsi):
(function(H) {
H.Tooltip.prototype.getAnchor = function(points, mouseEvent) {
var ret,
chart = this.chart,
inverted = chart.inverted,
plotTop = chart.plotTop,
plotLeft = chart.plotLeft,
plotX = 0,
plotY = 0,
yAxis,
xAxis;
points = H.splat(points);
// Pie uses a special tooltipPos
ret = points[0].tooltipPos;
// When tooltip follows mouse, relate the position to the mouse
if (this.followPointer && mouseEvent) {
if (mouseEvent.chartX === undefined) {
mouseEvent = chart.pointer.normalize(mouseEvent);
}
ret = [
mouseEvent.chartX - chart.plotLeft,
mouseEvent.chartY - plotTop
];
}
// When shared, use the average position
if (!ret) {
H.each(points, function(point) {
yAxis = point.series.yAxis;
xAxis = point.series.xAxis;
plotX += point.plotX + (!inverted && xAxis ? xAxis.left - plotLeft : 0);
plotY += (point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY) +
(!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151
});
plotX /= points.length;
plotY /= points.length;
ret = [
inverted ? chart.plotWidth - plotY : plotX,
this.shared && !inverted && points.length > 1 && mouseEvent ?
mouseEvent.chartY - plotTop : // place shared tooltip next to the mouse (#424)
inverted ? chart.plotHeight - plotX : plotY
];
}
// Add your event to Tooltip instances
this.event = mouseEvent;
return H.map(ret, Math.round);
}
})(Highcharts)
http://jsfiddle.net/2h951hdj/
Also you can wrap dragStart on the pointer and get exactly mouse position, in this case when you click on the chart area you will have the mouse position on the x-axis.
(function(H) {
H.wrap(H.Pointer.prototype, 'dragStart', function(proceed, e) {
let chart = this.chart;
chart.mouseIsDown = e.type;
chart.cancelClick = false;
chart.mouseDownX = this.mouseDownX = e.chartX;
chart.mouseDownY = this.mouseDownY = e.chartY;
chart.isZoomedByDrag = true;
console.log(chart.mouseDownX);
});
}(Highcharts));
Highcharts.chart('container', {
chart: {
events: {
load: function() {
let chart = this,
tooltip = chart.tooltip;
console.log(tooltip);
}
}
},
series: [{
data: [2, 5, 2, 3, 6, 5]
}],
});
Live demo: https://jsfiddle.net/BlackLabel/1b8rf9hc/
I'm experimenting with D3 version 4 force directed graphs and have looked at Jim Vallandingham's tutorial and code as a starting point.
http://vallandingham.me/bubble_chart_v4/
and am attempting to produce an animation similar to the example here from Nathan Yau
https://flowingdata.com/2016/08/23/make-a-moving-bubbles-chart-to-show-clustering-and-distributions/
I've stripped the bubble chart from Jim Vallandingham's code to what I think I need and can display the individual states by changing the index value, but for some reason the code does not want to animate between the different states. I assume the redraw function isn't working. It may be an obvious error or one made through complete ignorance, but if you can help it would be great.
Here's my code:
function bubbleChart() {
var width = 940;
var height = 600;
var center = { x: width / 2, y: height / 3 };
var years = ["0","2008", "2009", "2010"];
var yearCenters = {
2008: { x: width / 3, y: 2 * height / 3 },
2009: { x: width / 2, y: 2 * height / 3 },
2010: { x: 2 * width / 3, y: 2 * height / 3 }
};
// #v4 strength to apply to the position forces
var forceStrength = 0.03;
// These will be set in create_nodes and create_vis
var svg = null;
var bubbles = null;
var nodes = [];
var index= 0;
function charge(d) {
return -Math.pow(d.radius, 2.3) * forceStrength;
}
// Here we create a force layout
var simulation = d3.forceSimulation()
.velocityDecay(0.2)
.force('x', d3.forceX().strength(forceStrength).x(center.x))
.force('y', d3.forceY().strength(forceStrength).y(center.y))
.force('charge', d3.forceManyBody().strength(charge))
.on('tick', ticked);
// #v4 Force starts up automatically, which we don't want as there aren't any nodes yet.
simulation.stop();
// Nice looking colors
var fillColor = d3.scaleOrdinal()
.domain(['low', 'medium', 'high'])
.range(['#d84b2a', '#beccae', '#7aa25c']);
function createNodes(rawData) {
var myNodes = rawData.map(function (d) {
return {
id: d.id,
radius: 5,
value: +d.total_amount,
name: d.grant_title,
org: d.organization,
group: d.group,
year: d.start_year,
x: Math.random() * 900,
y: Math.random() * 800
};
});
// sort them to prevent occlusion of smaller nodes.
myNodes.sort(function (a, b) { return b.value - a.value; });
return myNodes;
}
/*
* Main entry point to the bubble chart.
*/
var chart = function chart(selector, rawData) {
// convert raw data into nodes data
nodes = createNodes(rawData);
// Create a SVG element inside the provided selector
// with desired size.
svg = d3.select(selector)
.append('svg')
.attr('width', width)
.attr('height', height);
// Bind nodes data to what will become DOM elements to represent them.
bubbles = svg.selectAll('.bubble')
.data(nodes, function (d) { return d.id; });
// Create new circle elements each with class `bubble`.
// There will be one circle.bubble for each object in the nodes array.
// Initially, their radius (r attribute) will be 0.
// #v4 Selections are immutable, so lets capture the
// enter selection to apply our transtition to below.
var bubblesE = bubbles.enter().append('circle')
.classed('bubble', true)
.attr('r', 0)
.attr('fill', function (d) { return fillColor(d.group); })
.attr('stroke', function (d) { return d3.rgb(fillColor(d.group)).darker(); })
.attr('stroke-width', 2)
// #v4 Merge the original empty selection and the enter selection
bubbles = bubbles.merge(bubblesE);
// Fancy transition to make bubbles appear, ending with the
// correct radius
bubbles.transition()
.duration(2000)
.attr('r', function (d) { return d.radius; });
// Set the simulation's nodes to our newly created nodes array.
// #v4 Once we set the nodes, the simulation will start running automatically!
simulation.nodes(nodes);
chart.redraw();
};
// Callback function that is called after every tick of the force simulation.
// These x and y values are modified by the force simulation.
function ticked() {
bubbles
.attr('cx', function (d) { return d.x; })
.attr('cy', function (d) { return d.y; });
}
chart.redraw = function (index){
simulation.force('x', d3.forceX().strength(forceStrength).x(nodePosX));
simulation.force('y', d3.forceY().strength(forceStrength).y(nodePosY));
simulation.alpha(1).restart();
}
function nodePosX(d) {
if (+d.year <= +years[index]) {
return yearCenters[d.year].x;
} else {
return center.x;
}
}
function nodePosY(d) {
if (+d.year <= +years[index]) {
return yearCenters[d.year].y;
} else {
return center.y;
}
}
// return the chart function from closure.
return chart;
}
var myBubbleChart = bubbleChart();
myBubbleChart('#vis', data);
for (i=0;i<4;i++){
setInterval(function(){myBubbleChart.redraw(i);}, 100);
}
I misunderstood how to use setInterval to redraw the chart, so it should be as follows:
var i = 0;
setInterval(function(){myBubbleChart.redraw(i++);}, 1000);
I am trying to create a scatter plot with unequal intervals on the X-axis using d3.js. My CSV data is shown here partially:
chr,pos,val
22,8947,8.58891099252
22,8978,4.65541559632
22,8996,6.33685790218
22,8997,9.00384002282
22,9006,4.39533823989
MT,9471,5.0655064583
MT,9472,7.83798949399
MT,9473,0.587797595352
MT,9474,4.6475160648
MT,9475,2.52382097771
MT,9476,7.8431366396
MT,9477,1.71519736769
MT,9478,2.61168595179
MT,9479,4.15061022346
MT,9470,7.1477707428
The number of pos values for each chr value may be different. In some cases, it could be 20, in others 100 and so on. I need to create a plot of val on the y-axis vs chr on the x-axis, with the x-interval for each chr being equal to the number of pos values for that chr. Although ordinal scale for the x-axis seems suitable here, it probably doesn't support unequal intervals. With linear scale, unequal intervals can be shown using polylinear scales, but the presence of alphabetic characters in chr mean no ticks are shown. Does anyone know how I can show unequal intervals in d3.js?
UPDATE:
I have some code here for the domain and ticks using a linear scale:
const x = d3.scale.linear()
.domain(input.map((d) => {
if (d.chr === 'MT') {
return 23;
}
if (d.chr === 'X') {
return 24;
}
return d.chr;
}))
.range(xTicks);
I can't understand how to show the ticks now.With this it shows 23 and 24 instead of MT and X.
I am not sure of this part:
const xAxis = d3.svg.axis().scale(x).orient('bottom').tickValues(input.map((d) => {
if (d.chr === 'MT') {
// returning a string here shows NaN
return 23;
}
if (d.chr === 'X') {
return 24;
}
return d.chr;
}));
Here is an example of how conditionally formatting the ticks using tickFormat (not tickValues).
Suppose the data is:
19, 20, 21, 22, 23, 24;
But we are going to change 23 for "X" and 24 for "MT" in the ticks. Click "run code snippet":
var data = [19, 20, 21, 22, 23, 24];
var width = 400, height = 100;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("heigth", height);
var xScale = d3.scale.linear()
.domain(d3.extent(data))
.range([0, width*.9]);
var xAxis = d3.svg.axis()
.orient("bottom")
.ticks(6)
.tickFormat(function(d){
if(d == 23){
return "X"
} else if(d==24){
return "MT"
} else {
return d
}
})
.scale(xScale);
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(20,20)")
.call(xAxis);
.axis path,
.axis line {
fill: none;
stroke: #aaa;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I started at chromosome 19 just to save some space, but you can get the general idea.
Using dc.js, I made an ordinal bar chart with numerical values. I chose ordinal chart because I wanted to add a x-tick for 'unknown'.
The problem is with y-axis. I cannot figure out to fix the axis labeling / scaling. As the picture shows, it alternates between [00, 20, 40, 60, 80]. I would like it to have a full range. I tried to manually insert y ticks, but there doesn't seem to be an accessor method for y-axis. Below is my code and the capture.
ghg2Chart /* dc.barChart('#volume-month-chart', 'chartGroup') */
.width(FULL_CHART_WIDTH)
.height(FULL_CHART_HEIGHT)
.margins({top: 10, right: 20, bottom: 40, left: 20})
.dimension(GHG2)
.group(GHG2Group)
.elasticY(true)
.centerBar(true)
.gap(5)
.round(dc.round.floor)
.alwaysUseRounding(true)
.x(d3.scale.ordinal().domain(GHG2bins))
.xUnits(dc.units.ordinal)
.renderHorizontalGridLines(true)
//Customize the filter displayed in the control span
.filterPrinter(function (filters) {
var filter = filters[0], s = '';
s += numberFormat(filter[0]) + ' -> ' + numberFormat(filter[1]);
return s;
})
.filterHandler(function (dimension, filter) {
var selectedRange = ghg2Chart.filter();
dimension.filter(function (d) {
var range;
var match = false;
// Iterate over each selected range and return if 'd' is within the range.
for (var i = 0; i < filter.length; i++) {
range = filter[i];
if (d >= range - .1 && d <= range) {
match = true;
break;
}
}
return selectedRange != null ? match : true;
});
return filter;
});
The only problem with your chart is that there is not enough space for the ticks, so the labels are getting clipped.
The default left margin is 30 pixels:
https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.marginMixin+margins
Try increasing the margin. The lazy way is:
chart.margins().left = 40;
But that's sort of confusing, so the more common way is
chart.margins({top: 10, right: 50, bottom: 30, left: 40});
It would be awesome for dc.js to do this automatically:
https://github.com/dc-js/dc.js/issues/713
but doing layout based on text sizes is rather difficult, so for now all this stuff is still manual (affects legends, axis labels, etc.)
I want to code a reusable chart in d3 - a "normalized" stacked bar chart. The data are scaled from 0% -100% on the Y-axis - see: http://bl.ocks.org/mbostock/3886394
I have understood that I need to calculate the inverse value of the data, to scale from 0 (Y: lowest value 0%) to 1 (Y: highest value 100%).
var y = d3.scale.linear()
.range([height, 0]); // no data domain
dataSet = dataSet.map(function (d) {
return d.map(function (p, i) {
return { x: i, y: (1 / (p.Value / 100))};
});
});
However, my scaling is not working correctly
please have a look # http://jsfiddle.net/dB96T/1/
thx!!