I try move a grouped element but after click and drag, the elements jumped away.
demo()
function demo() {
var tooltip = d3.select('body')
.append('div')
.attr('id','tooltip')
.style('position','absolute')
.style('opacity',0)
.style('background','lightsteelblue')
var svg = d3.select("body")
.append("svg")
.attr("width", 300)
.attr("height", 200)
.style("background", "#ececec")
add_grid(svg);
var data = [
{
text: "O",
x: 50,
y: 50
},
];
var g = svg.append('g')
var fontsize = 20;
var box = g.selectAll(".box")
.data(data)
.join('g')
.attr('class','box')
.attr("pointer-events", "all")
box.call(
d3.drag()
.on("start",function(event,d) {
d3.select(this).raise().classed("active", true);
d3.select('#tooltip')
.transition().duration(100)
.style('opacity', 1)
})
.on("drag",function(event,d) {
d.x = event.x
d.y = event.y
d3.select(this).attr('transform',`translate(${d.x},${d.y})`)
var desc = "(" + d.x.toFixed(1) +"," + d.y.toFixed(1) + ")"
d3.select('#tooltip')
.style('left', (event.x+2) + 'px')
.style('top', (event.y-2) + 'px')
.text(desc)
})
.on("end", function dragEnd(event,d) {
d3.select(this).classed("active", false);
d3.select('#tooltip').style('opacity', 0)}
))
.on('mouseover', function(event,d) {
})
.on('mouseout', function(event,d) {
})
.on('mousemove', function(event,d) {
})
.on("mousedown", function(){
})
.on("mouseup", function(){
});
var txt = box.append("text")
.attr("text-anchor", "middle")
.attr("dominant-baseline",'text-before-edge')//'central')//text-bottom
.attr("font-size", fontsize)
.attr("x", (d) => d.x)
.attr("y", (d) => d.y)
var tspan = txt.selectAll(".tspan")
.data((d) => d.text.split("\n"))
.join("tspan")
.attr("class", "tspan")
.attr("x", function (d) {
let x = +d3.select(this.parentNode).attr("x");
return x;
})
.attr("y", function (d,i) {
let y = +d3.select(this.parentNode).attr("y");
return y + i*fontsize * .9;
})
.text((d) => d);
box.each((d,i,n) => {
var bbox = d3.select(n[i]).node().getBBox()
var padding = 2
bbox.x -= padding
bbox.y -= padding
bbox.width += 2*padding
bbox.height += 2*padding
d.bbox = bbox
})
.attr('transform',d => `translate(${0},${-d.bbox.height/2})`)
.append('rect')
.attr('x',d => d.bbox.x)
.attr('y',d => d.bbox.y)
.attr('width', d => d.bbox.width)
.attr('height',d => d.bbox.height)
.attr('stroke','red')
.attr('fill','none')
add_dots(svg,data)
function add_dots(svg,data) {
svg.selectAll('.dots')
.data(data)
.join('circle')
.attr('class','dots')
.attr('r',2)
.attr('cx',d => d.x)
.attr('cy',d => d.y)
.attr('fill','red')
}
function add_grid(svg) {
var w = +svg.attr("width");
var step = 10;
var mygrid = function (d) {
return `M 0,${d} l ${w},0 M ${d},0 l 0,${w}`;
};
var grid = [];
for (var i = 0; i < w; i += step) {
grid.push(i);
}
svg
.append("g")
.selectAll(null)
.data(grid)
.enter()
.append("path")
.attr("d", (d) => mygrid(d))
.attr("fill", "none")
.attr("stroke", "green")
.attr("stroke-width", 0.5);
}
}
<script src="https://unpkg.com/d3#7.0.4/dist/d3.min.js"></script>
I've made a scatterplot and a choropleth map in the same web page. Data is stored in a .CSV and .json, and elements are linked with a "name" field.
I've made a tooltip on mouseover on both. I want now some interactivity beetween them: when mouse is over an element on scatterplot, this element on choropleth react and when mouse is over choropleth map scatterplot react.
Scatterplot and choropleth are in differents div with specifics ID and I don't how can I refeer from one to an other. I tried d3.select("this#scatterplot"); like this example but it doesn't work for me.
How can I select elements in differents DIV and differents functions ?
I want something like this :
function handleMouseOverMap(d, i) {
d3.select('this#choropleth').style('stroke-width', 3);
d3.select('this#scatterplot').attr('r', 8);
}
function handleMouseOverGraph(d, i) {
d3.select('this#scatterplot').attr('r', 8);
d3.select('this#choropleth').style('stroke-width', 3);
}
Code
<div id="scatterplot"></div>
<div id="choropleth"></div>
<script>
d3.queue()
.defer(d3.csv, 'data.csv', function (d) {
return {
name: d.name,
sau: +d.sau,
uta: +d.uta
}
})
.defer(d3.json, 'dept.json')
.awaitAll(initialize)
var color = d3.scaleThreshold()
.domain([150000, 300000, 450000])
.range(['#5cc567', '#e7dc2b', '#e59231', '#cb0000'])
function initialize(error, results) {
if (error) { throw error }
var data = results[0]
var features = results[1].features
var components = [
choropleth(features),
scatterplot(onBrush)
]
function update() {
components.forEach(function (component) { component(data) })
}
function onBrush(x0, x1, y0, y1) {
var clear = x0 === x1 || y0 === y1
data.forEach(function (d) {
d.filtered = clear ? false
: d.uta < x0 || d.uta > x1 || d.sau < y0 || d.sau > y1
})
update()
}
update()
}
/* Graphique */
function scatterplot(onBrush) {
var margin = { top: 10, right: 15, bottom: 40, left: 75 }
var width = 680 - margin.left - margin.right
var height = 550 - margin.top - margin.bottom
var x = d3.scaleLinear()
.range([0, width])
var y = d3.scaleLinear()
.range([height, 0])
// Tooltip
var xValue = function(d) { return d.sau;};
var yValue = function(d) { return d.uta;};
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var xAxis = d3.axisBottom()
.scale(x)
.tickFormat(d3.format(''))
var yAxis = d3.axisLeft()
.scale(y)
.tickFormat(d3.format(''))
// Selection
var brush = d3.brush()
.extent([[0, 0], [width, height]])
.on('start brush', function () {
var selection = d3.event.selection
var x0 = x.invert(selection[0][0])
var x1 = x.invert(selection[1][0])
var y0 = y.invert(selection[1][1])
var y1 = y.invert(selection[0][1])
onBrush(x0, x1, y0, y1)
})
var svg = d3.select('#scatterplot')
.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 bg = svg.append('g')
var gx = svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
var gy = svg.append('g')
.attr('class', 'y axis')
gx.append('text')
.attr('x', width)
.attr('y', 35)
.style('text-anchor', 'end')
.style('fill', '#000')
.style('font-weight', 'bold')
.text('UTA')
gy.append('text')
.attr('transform', 'rotate(-90)')
.attr('x', 0)
.attr('y', -55)
.style('text-anchor', 'end')
.style('fill', '#000')
.style('font-weight', 'bold')
.text('SAU - ha')
svg.append('g')
.attr('class', 'brush')
.call(brush)
return function update(data) {
x.domain(d3.extent(data, function (d) { return d.uta })).nice()
y.domain(d3.extent(data, function (d) { return d.sau })).nice()
gx.call(xAxis)
gy.call(yAxis)
var bgRect = bg.selectAll('rect')
.data(d3.pairs(d3.merge([[y.domain()[0]], color.domain(), [y.domain()[1]]])))
bgRect.exit().remove()
bgRect.enter().append('rect')
.attr('x', 0)
.attr('width', width)
.merge(bgRect)
.attr('y', function (d) { return y(d[1]) })
.attr('height', function (d) { return y(d[0]) - y(d[1]) })
.style('fill', function (d) { return color(d[0]) })
var circle = svg.selectAll('circle')
.data(data, function (d) { return d.name })
circle.exit().remove()
circle.enter().append('circle')
.attr('r', 4)
.style('stroke', '#fff')
.merge(circle)
.attr('cx', function (d) { return x(d.uta) })
.attr('cy', function (d) { return y(d.sau) })
.style('fill', function (d) { return color(d.sau) })
.style('opacity', function (d) { return d.filtered ? 0.5 : 1 })
.style('stroke-width', function (d) { return d.filtered ? 1 : 2 })
// Event
.on("mouseover", function(d) {
tooltipOverGraph.call(this, d);
handleMouseOverGraph.call(this, d);
})
.on("mouseout", function(d) {
tooltipOutGraph.call(this, d);
handleMouseOutGraph.call(this, d);
})
}
// Tooltip
function tooltipOverGraph(d) {
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(d["name"] + "<br>" + xValue(d)
+ " ha" +", " + yValue(d) + " UTA" )
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 28) + "px");
}
function tooltipOutGraph(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
}
}
// Create Event Handlers for mouse
function handleMouseOverGraph(d, i) {
d3.select(this).attr('r', 8);
}
function handleMouseOutGraph(d, i) {
d3.select(this).attr('r', 4);
}
/* Carte */
function choropleth(features) {
var width = 680
var height = 550
// Tooltip
var xValue = function(d) { return d.sau;};
var yValue = function(d) { return d.uta;};
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
// Projection et centrage de la carte
var projection = d3.geoMercator()
.center([ 3, 46.5 ])
.scale([width * 3.1])
.translate([width / 2, height / 2])
var path = d3.geoPath().projection(projection)
var svg = d3.select('#choropleth')
.append('svg')
.attr('width', width)
.attr('height', height)
svg.selectAll('path')
.data(features)
.enter()
.append('path')
.attr('d', path)
.style('stroke', '#fff')
.style('stroke-width', 1)
// Event
.on("mouseover", function(d) {
tooltipOverMap.call(this, d);
handleMouseOverMap.call(this, d);
})
.on("mouseout", function(d) {
tooltipOutMap.call(this, d);
handleMouseOutMap.call(this, d);
})
// Tooltip
function tooltipOverMap(d) {
tooltip.transition()
.duration(200)
.style("opacity", .9);
tooltip.html(d["name"] + "<br>" + xValue(d)
+ " ha" +", " + yValue(d) + " UTA" )
.style("left", (d3.event.pageX + 5) + "px")
.style("top", (d3.event.pageY - 28) + "px");
}
function tooltipOutMap(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
}
return function update(data) {
svg.selectAll('path')
.data(data, function (d) { return d.name || d.properties.name })
.style('fill', function (d) { return d.filtered ? '#ddd' : color(d.sau) })
}
}
// Create Event Handlers for mouse
function handleMouseOverMap(d, i) {
d3.select(this).style('stroke-width', 3);
}
function handleMouseOutMap(d, i) {
d3.select(this).style('stroke-width', 1);
}
</script>
Example
First, add distinct class attributes to the rect and circle items when you enter + append them:
bgRect.enter().append('rect')
.attr('class', function(d,i) { return 'classRect' + i; })
circle.enter().append('circle')
.attr('class', function(d,i) { return 'classCircle' + i; })
Then, update your mouse over functions:
function handleMouseOverMap(d) {
// update the choropleth //
d3.select(d).style('stroke-width', 3);
// update the scatterplot //
// capture the number contained in class (e.g. "1" for "classRect1")
var i = d3.select(d).class.substr(-1);
// select corresponding circle in scatter and update
d3.select('circle.classCircle'+i).attr('r', 8);
}
function handleMouseOverGraph(d) {
// update the scatter //
d3.select(d).attr('r', 8);
// update the choropleth //
// capture the number contained in class (e.g. "1" for "classCircle1")
var i = d3.select(d).class.substr(-1);
// select corresponding rect in the choropleth and update
d3.select('rect.classRect'+i).style('stroke-width', 3);
}
I have a project that almost works the way I want. When a smaller dataset is added, slices are removed. It fails when a larger dataset is added. The space for the arc is added but no label or color is added for it.
This is my enter() code:
newArcs.enter()
.append("path")
.attr("stroke", "white")
.attr("stroke-width", 0.8)
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc);
What am I doing wrong?
I've fixed the code such that it works now:
// Tween Function
var arcTween = function(a) {
var i = d3.interpolate(this.current || {}, a);
this.current = i(0);
return function(t) {
return arc(i(t));
};
};
// Setup all the constants
var duration = 500;
var width = 500
var height = 300
var radius = Math.floor(Math.min(width / 2, height / 2) * 0.9);
var colors = ["#d62728", "#ff9900", "#004963", "#3497D3"];
// Test Data
var d2 = [{
label: 'apples',
value: 20
}, {
label: 'oranges',
value: 50
}, {
label: 'pears',
value: 100
}];
var d1 = [{
label: 'apples',
value: 100
}, {
label: 'oranges',
value: 20
}, {
label: 'pears',
value: 20
}, {
label: 'grapes',
value: 20
}];
// Set the initial data
var data = d1
var updateChart = function(dataset) {
arcs = arcs.data(donut(dataset), function(d) { return d.data.label });
arcs.exit().remove();
arcs.enter()
.append("path")
.attr("stroke", "white")
.attr("stroke-width", 0.8)
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc);
arcs.transition()
.duration(duration)
.attrTween("d", arcTween);
sliceLabel = sliceLabel.data(donut(dataset), function(d) { return d.data.label });
sliceLabel.exit().remove();
sliceLabel.enter()
.append("text")
.attr("class", "arcLabel")
.attr("transform", function(d) {
return "translate(" + (arc.centroid(d)) + ")";
})
.attr("text-anchor", "middle")
.style("fill-opacity", function(d) {
if (d.value === 0) {
return 1e-6;
} else {
return 1;
}
})
.text(function(d) {
return d.data.label;
});
sliceLabel.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + (arc.centroid(d)) + ")";
})
.style("fill-opacity", function(d) {
if (d.value === 0) {
return 1e-6;
} else {
return 1;
}
});
};
var color = d3.scale.category20();
var donut = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arc = d3.svg.arc()
.innerRadius(radius * .4)
.outerRadius(radius);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var arc_grp = svg.append("g")
.attr("class", "arcGrp")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
var label_group = svg.append("g")
.attr("class", "lblGroup")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
var arcs = arc_grp.selectAll("path");
var sliceLabel = label_group.selectAll("text");
updateChart(data);
// returns random integer between min and max number
function getRand() {
var min = 1,
max = 2;
var res = Math.floor(Math.random() * (max - min + 1) + min);
//console.log(res);
return res;
}
// Update the data
setInterval(function(model) {
var r = getRand();
return updateChart(r == 1 ? d1 : d2);
}, 2000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
I am trying to implement tooltips and a zoom on a graph, the tooltips work fine however I am having issues with the zoom function. I get an error on the debugger that says: Uncaught TypeError: zoomed.x is not a function. This occurs at the line 'return zoomed.x(x);' I am not sure how to change the code to get it to work.
//Builder for REal time flow chart
define((function () {
var categories = [
{
Name: "MediaServiceIndexes",
Title: "Media",
Style: "background-color:#ffffff;font-color:blue;",
Text: "Here are the top media companies with whom our customers shop.",
TitleStyle: "margin-left:0px"
},
];
var url = App.SiteUrl + "/Data/RequestDataFrom";
var svg, width, numberChecker;
var patterns = d3.scale.ordinal()
.range([
"url(#blue1)", "url(#yellow1)",
"url(#blue2)", "url(#yellow2)",
"url(#blue3)", "url(#yellow3)",
"url(#blue4)", "url(#yellow4)",
"url(#blue5)", "url(#yellow5)"
]);
var grayFill = "url(#gray)";
var colors = d3.scale.category10();
var getValues = function (values) {
var result = [];
try {
values.forEach(function (item) {
var o = {
Month: moment(item.month + "01", "YYYYMMDD"),
Value: parseFloat(item.change_in_market_share, 10)
};
var vendor = item.vendor.replace(/'/g, "");
//var e = result.find(function (it) { return it.Vendor == vendor; });
var e = false;
for (var x in result) {
if (result.hasOwnProperty(x) && typeof result[x] != "function") {
if (result[x].Vendor == vendor) {
e = (result[x]);
}
}
}
if (e) {
e.Values.push(o);
} else {
result.push({
Visible: true,
Vendor: vendor,
Values: [o]
});
}
});
result.forEach(function (item, i) {
item.Index = i;
item.Values.sort(function (a, b) {
return a.Month - b.Month;
});
});
} catch (e) {
result = null;
};
return result;
};
var createChart = function (chartElement, cd) {
var chartData = cd; //chart data is being passed in using cd via createChart function
//chart gives the location of the chart,
var chart = function (el, data) {
var margin = {
top: 20,
right: 180,
bottom: 50,
left: 110
};
var elem = el;
var chartEl = chartElement;
var fillData = [];
var defs,
gs,
height,
line,
maxDays,
minDays,
minValue,
maxValue,
x,
xAxis,
y,
yAxis;
var initialiseData = function (dataValues) {
minDays = d3.min(dataValues,
function (m) {
return d3.min(m.Values,
function (d) {
return d.Month;
});
});
maxDays = d3.max(dataValues,
function (m) {
return d3.max(m.Values,
function (d) {
return d.Month;
});
});
minValue = d3.min(chartData,
function (m) {
return d3.min(m.Values,
function (d) {
return d.Value;
});
});
maxValue = d3.max(chartData,
function (m) {
return d3.max(m.Values,
function (d) {
return d.Value;
});
});
console.log('min days: ' + minDays);
console.log('max days: ' + maxDays);
console.log('min value: ' + minValue);
console.log('max value: ' + maxValue);
dataValues.forEach(function (item) {
var nu = $.extend(true, {}, item);
nu.Values.push({ Month: maxDays, Value: minValue });
nu.Values.push({ Month: minDays, Value: minValue });
fillData.push(nu);
});
};
//initialise scales
var configSize = function () {
if (isNaN(numberChecker) === true) {
numberChecker = $(chartEl).width();
width = numberChecker;
}
width = 750;
height = 500 - margin.top - margin.bottom;
x = d3.scale.linear()
.range([0, width])
.domain([minDays, maxDays]);
y = d3.scale.linear()
.range([height, 0])
.domain([minValue, maxValue]);
$('.adjustmentZoom .tick text').attr('y', '25');
//initialise axis
xAxis = d3.svg.axis()
.scale(x)
.tickFormat(function (d, i) {
if (Math.floor(d) !== d) {
} else {
return moment(d).format("MMM YY");
}
})
.orient('bottom');
yAxis = d3.svg.axis()
.scale(y)
.tickFormat(function (d) {
return d3.round(d, 3) + "%"
})
.orient('left');
$('#clipper rect').attr('width', width);
$('.zoom-panel').attr('width', width);
};
var svgTransform = function(d) {
return "translate(" + x(d.Month) + "," + y(d.Value) + ")";
};
var zoomed = function () {
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
svg.selectAll(".datapoint").attr("transform", svgTransform);
};
//the path generator for the line chart
var initialise = function () {
line = d3.svg.line()
.interpolate(
'cardinal')
.x(function (d) {
return x(d.Month);
})
.y(function (d) {
return y(d.Value);
});
var zoomBeh = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([
1,
500
])
.on('zoom', zoomed);
//elem is window
svg = d3.select(elem).append('div').attr('id', 'scatter').append('svg')
.attr('width', '100%')
.attr('height', '100%')
.attr('viewBox', '0 0 1000 550')
.attr('preserveAspectRatio', 'xMinYMin meet')
.append('g')
.attr("class", "line-container")
.attr('transform',
function () {
if (window.innerWidth > 650) {
var marginWidth = margin.left;
}
else if (window.innerWidth <= 650 && window.innerWidth > 549) {
var marginWidth = margin.left + 30;
}
else if (window.innerWidth <= 549) {
var marginWidth = margin.left + 50;
}
return 'translate(' + marginWidth + ',' + margin.top + ')'
}).call(zoomBeh);
svg.append('rect')
.attr('class', 'zoom-panel')
.attr('width', width)
.attr('height', height)
.call(zoomBeh);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis)
.selectAll('text');
svg.append('text')
.attr('x', 400)
.attr('y', (height + 70))
.style('text-anchor', 'middle')
.attr('class', 'xTitle')
.style('font-weight', '500')
.text('Date by month');
svg.append('g').attr('class', 'y axis').attr('transform', 'translate(0,0)').style('text-anchor', 'end').call(yAxis);
svg.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', -100)
.attr('x', -200)
.attr('dy', '1em')
.attr('class', 'yTitle')
.style('text-anchor', 'middle')
.style('font-weight', '500')
.text('Share prices in percentage');
//zoom.scaleExtent([
// 1,
// moment(maxDays).diff(minDays, "months")
//]);
}
var tooltip = function () {
svg = d3.select(elem)
.append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
};
//draw calls drawData which will draw the lines of the chart
var draw = function () {
var drawData = function (dat, className, baseFill, area) {
//var selection (supermarket)
var supermarket, supermarketEnter;
supermarket = svg.selectAll('.' + className)
.data(dat,
function (c) {
return c.Vendor;
});
//var new selection (supermarketEnter)
supermarketEnter = supermarket.enter()
.append('g')
.attr('class', className)
.attr('data-vendor', function(d) { return d.Vendor; })
.attr('width', width)
.attr('height', height);
supermarketEnter.append('path');
//update new selection
supermarketEnter.attr('clip-path', 'url(#clipper)').attr('class', 'line').attr('id', function (d) { return d.Vendor; });
supermarketEnter
.selectAll("circle")
.data(function (d) {
return d.Values;
})
.enter()
.append('circle')
.attr('class', 'datapoint')
.attr('r', 4)
.style('fill',
function (d, i, j) {
return dat[j].Visible ? baseFill(j) : grayFill;
})
.attr('transform',
function (d) {
return 'translate(' + x(d.Month) + ',' + y(d.Value) + ')';
})
.on('mouseover',
function (d, i, j) {
d3.select('.tooltip').style('opacity', '1');
d3.select('.tooltip')
.html(dat[j].Vendor +
'<br/> (' +
moment(d.Month).format("MMM YYYY") +
', ' +
d.Value.toPrecision(2) +
'% )')
.style('left',
function () {
if (window.innerWidth >= 1200) {
var newWidth = d3.event.pageX -
($(chartEl).width() / 2) +
'px'
} else if (window.innerWidth < 1200) {
var newWidth = d3.event.pageX - ($(chartEl).width() / 10) + 'px'
}
return newWidth;
})
.style('top', (d3.event.pageY) - 300 + 'px');
})
.on("mouseout",
function (d) {
d3.select('.tooltip')
.style('transition', '500')
.style('opacity', 0)
.style('color', d3.select(this).style('fill'));
});
supermarket.select('path')
.transition()
.duration(500)
.attr('d',
function (d) {
return line(d.Values);
});
var path = supermarket.select('path')
.style('stroke', function (d, i) {
return (d.Visible ? baseFill(d.Index) : grayFill);
});
if (area) {
path.style('fill', function (d, i) { return (d.Visible ? baseFill(d.Index) : grayFill); });
}
supermarket.exit().remove();
supermarket.order();
}
svg.selectAll('.x.axis').call(xAxis);
svg.selectAll('.y.axis').call(yAxis);
drawData(chartData, 'supermarket', patterns, false);
return zoomed.x(x);
};
var render = function () {
configSize();
draw();
};
//insertion of drawLegend into chart function so that this will draw as well as the lines of the chart.
var drawLegend = function (dat, className) {
//DATA JOIN
//Join new data with old elements, if any.
var supermarket, supermarketEnter;
supermarket = svg.selectAll('legend_' + className)
.data(dat, function (c) { return c.Vendor; });
//UPDATE
//Update old elements as needed.
supermarket.attr('class', 'update');
//ENTER + UPDATE
//After merging the entered elements with the update selection, apply operations to both.
supermarketEnter = supermarket.enter().append('g')
.attr('class', 'legend_' + className)
.attr('data-vendor', function (d) { return d.Vendor; });
supermarketEnter
.append('text')
.attr('class', 'supermarket-name')
.attr('data-vendor', function (d) { return d.Vendor; });//
supermarket.select('text.supermarket-name')
.attr('x', width + 25)
.attr('y', function (d, i) { return (i * 1.25) * 20; })
.attr('dy', '.35em')
.text(function (d) {
return d.Vendor;
})
.on('click', function (d, i, j) {
dat[i].Visible = !dat[i].Visible;
var newOpacity = dat[i].Visible ? 1 : 0;
d3.select("[data-vendor='" + dat[i].Vendor + "']").style('opacity', newOpacity);
});
supermarketEnter.append('rect')
.attr('class', 'supermarket-dot');
supermarket.select('rect.supermarket-dot')
.attr('x', width + 10).attr('y', function (d, i) {
return ((i * 1.25) * 20) - 5;
})
.attr('width', 12)
.attr('height', 12)
.style('fill', function (d, i) {
return patterns(d.Index);
});
//EXIT
//Remove old elements as needed.
supermarket.exit().remove();
};
//insertion of drawLegend into chart function originally placed after configSize(); draw(); };
var toggleLines = function () {
var dat = $(this.closest("g")).data();
var obj = chartData.find(function (item) { return item.Vendor === dat.vendor; });
if (obj.Visible) {
obj.Visible = false;
} else {
obj.Visible = true;
}
chartData.sort(function (a, b) {
if (a.Visible === b.Visible) return 0;
if (a.Visible && !b.Visible) return 1;
return -1;
});
draw();
};
initialiseData(data);
configSize();
initialise();
draw();
drawLegend(chartData, 'supermarket');
tooltip();
$('.adjustmentZoom .tick text').attr('y', '25');
return {
Draw: render
};
};
var c = chart(chartElement, chartData);
//draw draws the lines of the chart. chart function draws the space arou
$(window).on("resize", function () {
c.Draw();
$('.adjustmentZoom .tick text').attr('y', '25');
});
}
var initialiseElement = function () {
categories.forEach(function (item) {
$(".slidesIntroduction").append('<div class="">\
<div class="insight-chart chart tradingData ' + item.Name + '" style="' + item.Style + '">\
<h3 class="chartTitle" ><br />\All ' + item.Title + '</h3>\
<p style="' + item.TitleStyle + '">\
<br />\
' + item.Text + '\
</p>\
</div>\
<//div>');
});
};
var renderInsightData = function () {
initialiseElement();
categories.forEach(function (item) {
if (item.DataValues) {
createChart("." + item.Name, item.DataValues);
}
});
};
var startWork = function () {
var catCount = 0;
var startRequest = function () {
App.Modules.ServerComms.PollForData({
Url: url,
Data: { src: categories[catCount].Name },
Success: processResponse,
});
};
var processResponse = function (response) {
categories[catCount].DataValues = getValues(response);
catCount++;
if (catCount < categories.length) {
startRequest();
} else {
renderInsightData();
$(".spinner").removeClass("spinner");
}
};
startRequest();
};
return {
Name: "Introduction",
Init: function () { },
Start: startWork
}
})());
//Builder for REal time flow chart
define((function () {
var categories = [
{
Name: "MediaServiceIndexes",
Title: "Media",
Style: "background-color:#ffffff;font-color:blue;",
Text: "Here are the top media companies with whom our customers shop.",
TitleStyle: "margin-left:0px"
},
];
var url = App.SiteUrl + "/Data/RequestDataFrom";
var svg, width, numberChecker;
var patterns = d3.scale.ordinal()
.range([
"url(#blue1)", "url(#yellow1)",
"url(#blue2)", "url(#yellow2)",
"url(#blue3)", "url(#yellow3)",
"url(#blue4)", "url(#yellow4)",
"url(#blue5)", "url(#yellow5)"
]);
var grayFill = "url(#gray)";
var colors = d3.scale.category10();
var getValues = function (values) {
var result = [];
try {
values.forEach(function (item) {
var o = {
Month: moment(item.month + "01", "YYYYMMDD"),
Value: parseFloat(item.change_in_market_share, 10)
};
var vendor = item.vendor.replace(/'/g, "");
//var e = result.find(function (it) { return it.Vendor == vendor; });
var e = false;
for (var x in result) {
if (result.hasOwnProperty(x) && typeof result[x] != "function") {
if (result[x].Vendor == vendor) {
e = (result[x]);
}
}
}
if (e) {
e.Values.push(o);
} else {
result.push({
Visible: true,
Vendor: vendor,
Values: [o]
});
}
});
result.forEach(function (item, i) {
item.Index = i;
item.Values.sort(function (a, b) {
return a.Month - b.Month;
});
});
} catch (e) {
result = null;
};
return result;
};
var createChart = function (chartElement, cd) {
var chartData = cd; //chart data is being passed in using cd via createChart function
//chart gives the location of the chart,
var chart = function (el, data) {
var margin = {
top: 20,
right: 180,
bottom: 50,
left: 110
};
var elem = el;
var chartEl = chartElement;
var fillData = [];
var defs,
gs,
height,
line,
maxDays,
minDays,
minValue,
maxValue,
x,
xAxis,
zoomBeh,
y,
yAxis;
var initialiseData = function (dataValues) {
minDays = d3.min(dataValues,
function (m) {
return d3.min(m.Values,
function (d) {
return d.Month;
});
});
maxDays = d3.max(dataValues,
function (m) {
return d3.max(m.Values,
function (d) {
return d.Month;
});
});
minValue = d3.min(chartData,
function (m) {
return d3.min(m.Values,
function (d) {
return d.Value;
});
});
maxValue = d3.max(chartData,
function (m) {
return d3.max(m.Values,
function (d) {
return d.Value;
});
});
console.log('min days: ' + minDays);
console.log('max days: ' + maxDays);
console.log('min value: ' + minValue);
console.log('max value: ' + maxValue);
dataValues.forEach(function (item) {
var nu = $.extend(true, {}, item);
nu.Values.push({ Month: maxDays, Value: minValue });
nu.Values.push({ Month: minDays, Value: minValue });
fillData.push(nu);
});
};
//initialise scales
var configSize = function () {
if (isNaN(numberChecker) === true) {
numberChecker = $(chartEl).width();
width = numberChecker;
}
width = 750;
height = 500 - margin.top - margin.bottom;
x = d3.scale.linear()
.range([0, width])
.domain([minDays, maxDays]);
y = d3.scale.linear()
.range([height, 0])
.domain([minValue, maxValue]);
$('.adjustmentZoom .tick text').attr('y', '25');
//initialise axis
xAxis = d3.svg.axis()
.scale(x)
.tickFormat(function (d, i) {
if (Math.floor(d) !== d) {
} else {
return moment(d).format("MMM YY");
}
})
.orient('bottom');
yAxis = d3.svg.axis()
.scale(y)
.tickFormat(function (d) {
return d3.round(d, 3) + "%"
})
.orient('left');
$('#clipper rect').attr('width', width);
$('.zoom-panel').attr('width', width);
};
//the path generator for the line chart
var initialise = function () {
line = d3.svg.line()
.interpolate(
'cardinal')
.x(function (d) {
return x(d.Month);
})
.y(function (d) {
return y(d.Value);
});
zoomBeh = d3.behavior.zoom()
.x(x)
.y(y)
.scaleExtent([
1,
500
])
.on('zoom', zoomed);
//elem is window
svg = d3.select(elem).append('div').attr('id', 'scatter').append('svg')
.attr('width', '100%')
.attr('height', '100%')
.attr('viewBox', '0 0 1000 550')
.attr('preserveAspectRatio', 'xMinYMin meet')
.append('g')
.attr("class", "line-container")
.attr('transform',
function () {
if (window.innerWidth > 650) {
var marginWidth = margin.left;
}
else if (window.innerWidth <= 650 && window.innerWidth > 549) {
var marginWidth = margin.left + 30;
}
else if (window.innerWidth <= 549) {
var marginWidth = margin.left + 50;
}
return 'translate(' + marginWidth + ',' + margin.top + ')'
}).call(zoomBeh).on("dblclick.zoom", null);
svg.append('rect')
.attr('class', 'zoom-panel')
.attr('width', width)
.attr('height', height)
.call(zoomBeh);
svg.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis)
.selectAll('text');
svg.append('text')
.attr('x', 400)
.attr('y', (height + 70))
.style('text-anchor', 'middle')
.attr('class', 'xTitle')
.style('font-weight', '500')
.text('Date by month');
svg.append('g').attr('class', 'y axis').attr('transform', 'translate(0,0)').style('text-anchor', 'end').call(yAxis);
svg.append('text')
.attr('transform', 'rotate(-90)')
.attr('y', -100)
.attr('x', -200)
.attr('dy', '1em')
.attr('class', 'yTitle')
.style('text-anchor', 'middle')
.style('font-weight', '500')
.text('Share prices in percentage');
//zoom.scaleExtent([
// 1,
// moment(maxDays).diff(minDays, "months")
//]);
}
//draw calls drawData which will draw the lines of the chart
var draw = function () {
var drawData = function (dat, className, baseFill, area) {
//var selection (supermarket)
var supermarket, supermarketEnter;
supermarket = svg.selectAll('.' + className)
.data(dat,
function (c) {
return c.Vendor;
});
//var new selection (supermarketEnter)
supermarketEnter = supermarket.enter()
.append('g')
.attr('class', className)
.attr('data-vendor', function(d) { return d.Vendor; })
.attr('width', width)
.attr('height', height);
supermarketEnter.append('path');
//update new selection
supermarketEnter.attr('clip-path', 'url(#clipper)').attr('class', 'line').attr('id', function (d) { return d.Vendor; });
supermarket.select('path')
.transition()
.attr('class', 'line-data')
.duration(500)
.attr('d',
function (d) {
return line(d.Values);
});
var path = supermarket.select('path')
.style('stroke', function (d, i) {
return (d.Visible ? baseFill(d.Index) : grayFill);
});
if (area) {
path.style('fill', function (d, i) { return (d.Visible ? baseFill(d.Index) : grayFill); });
}
supermarketEnter
.selectAll("circle")
.data(function (d) {
return d.Values;
})
.enter()
.append('circle')
.attr('class', 'datapoint')
.attr('r', 4)
.style('fill',
function (d, i, j) {
return dat[j].Visible ? baseFill(j) : grayFill;
})
.attr('transform',
function (d) {
return 'translate(' + x(d.Month) + ',' + y(d.Value) + ')';
})
.on('mouseover',
function (d, i, j) {
d3.select('.tooltip').style('opacity', '1');
d3.select('.tooltip')
.html(dat[j].Vendor +
'<br/> (' +
moment(d.Month).format("MMM YYYY") +
', ' +
d.Value.toPrecision(2) +
'% )')
.style('left',
function () {
if (window.innerWidth >= 1200) {
var newWidth = d3.event.pageX -
($(chartEl).width() / 2) +
'px'
} else if (window.innerWidth < 1200) {
var newWidth = d3.event.pageX - ($(chartEl).width() / 10) + 'px'
}
return newWidth;
})
.style('top', (d3.event.pageY) - 300 + 'px');
})
.on("mouseout",
function (d) {
d3.select('.tooltip')
.style('transition', '500')
.style('opacity', 0)
.style('color', d3.select(this).style('fill'));
});
supermarket.exit().remove();
supermarket.order();
}
svg.selectAll('.x.axis').call(xAxis);
svg.selectAll('.y.axis').call(yAxis);
drawData(chartData, 'supermarket', patterns, false);
};
var render = function () {
configSize();
draw();
};
//insertion of drawLegend into chart function so that this will draw as well as the lines of the chart.
var drawLegend = function (dat, className) {
//DATA JOIN
//Join new data with old elements, if any.
var supermarket, supermarketEnter;
supermarket = svg.selectAll('legend_' + className)
.data(dat, function (c) { return c.Vendor; });
//UPDATE
//Update old elements as needed.
supermarket.attr('class', 'update');
//ENTER + UPDATE
//After merging the entered elements with the update selection, apply operations to both.
supermarketEnter = supermarket.enter().append('g')
.attr('class', 'legend_' + className)
.attr('data-vendor', function (d) { return d.Vendor; });
supermarketEnter
.append('text')
.attr('class', 'supermarket-name')
.attr('data-vendor', function (d) { return d.Vendor + "-legend"; });//
supermarket.select('text.supermarket-name')
.attr('x', width + 25)
.attr('y', function (d, i) { return (i * 1.25) * 20; })
.attr('dy', '.35em')
.text(function (d) {
return d.Vendor;
})
.on('click', function (d, i, j) {
dat[i].Visible = !dat[i].Visible;
var newOpacity = dat[i].Visible ? 1 : 0;
d3.select("[data-vendor='" + dat[i].Vendor + "']").style('opacity', newOpacity);
});
supermarketEnter.append('rect')
.attr('class', 'supermarket-dot');
supermarket.select('rect.supermarket-dot')
.attr('x', width + 10).attr('y', function (d, i) {
return ((i * 1.25) * 20) - 5;
})
.attr('width', 12)
.attr('height', 12)
.style('fill', function (d, i) {
return patterns(d.Index);
});
//EXIT
//Remove old elements as needed.
supermarket.exit().remove();
d3.select(elem).append('div').attr('class', 'tooltip').style('opacity', 0);
return createChart().chart().initialise().zoomBeh.x(x);
};
var svgTransform = function (d) {
return "translate(" + x(d.Month) + "," + y(d.Value) + ")";
};
var lineTransform = function(d) {
return line(d.Values);
}
var zoomed = function () {
svg.select(".x.axis").call(xAxis);
svg.select(".y.axis").call(yAxis);
svg.selectAll(".datapoint").attr("transform", svgTransform);
svg.selectAll(".line-data").attr("d", lineTransform);
};
//insertion of drawLegend into chart function originally placed after configSize(); draw(); };
var toggleLines = function () {
var dat = $(this.closest("g")).data();
var obj = chartData.find(function (item) { return item.Vendor === dat.vendor; });
if (obj.Visible) {
obj.Visible = false;
} else {
obj.Visible = true;
}
chartData.sort(function (a, b) {
if (a.Visible === b.Visible) return 0;
if (a.Visible && !b.Visible) return 1;
return -1;
});
draw();
};
initialiseData(data);
configSize();
initialise();
draw();
drawLegend(chartData, 'supermarket');
tooltip();
$('.adjustmentZoom .tick text').attr('y', '25');
return {
Draw: render
};
};
var c = chart(chartElement, chartData);
//draw draws the lines of the chart. chart function draws the space arou
$(window).on("resize", function () {
c.Draw();
$('.adjustmentZoom .tick text').attr('y', '25');
});
}
var initialiseElement = function () {
categories.forEach(function (item) {
$(".slidesIntroduction").append('<div class="">\
<div class="insight-chart chart tradingData ' + item.Name + '" style="' + item.Style + '">\
<h3 class="chartTitle" ><br />\All ' + item.Title + '</h3>\
<p style="' + item.TitleStyle + '">\
<br />\
' + item.Text + '\
</p>\
</div>\
<//div>');
});
};
var renderInsightData = function () {
initialiseElement();
categories.forEach(function (item) {
if (item.DataValues) {
createChart("." + item.Name, item.DataValues);
}
});
};
var startWork = function () {
var catCount = 0;
var startRequest = function () {
App.Modules.ServerComms.PollForData({
Url: url,
Data: { src: categories[catCount].Name },
Success: processResponse,
});
};
var processResponse = function (response) {
categories[catCount].DataValues = getValues(response);
catCount++;
if (catCount < categories.length) {
startRequest();
} else {
renderInsightData();
$(".spinner").removeClass("spinner");
}
};
startRequest();
};
return {
Name: "Introduction",
Init: function () { },
Start: startWork
}
})());
I'm having difficulty to plot the horizontal grid according the y-axis tick values. I'm able to plot the grid on y-axis but it's not coming according the Y-axis co-ordinates.
Bar chart
Here is my fiddle
var data = [["Since Mar 10, 2015",150], ["1 year",-17.1], ["3 year",6.9],["Since Mar 10, 2010",100]];
d3.select("#example")
.datum(data)
.call(columnChart()
.width(320)
.height(240)
.x(function(d, i) { return d[0]; })
.y(function(d, i) { return d[1]; }));
function columnChart() {
var margin = {top: 30, right: 10, bottom: 50, left: 50},
width = 20,
height = 20,
xRoundBands = 0.6,
xValue = function(d) { return d[0]; },
yValue = function(d) { return d[1]; },
xScale = d3.scale.ordinal(),
yScale = d3.scale.linear(),
yAxis = d3.svg.axis().scale(yScale).orient("left"),
xAxis = d3.svg.axis().scale(xScale);
var isNegative = false;
function chart(selection) {
selection.each(function(data) {
// Convert data to standard representation greedily;
// this is needed for nondeterministic accessors.
for(var i=0; i< data.length; i++){
if(data[i][1] < 0){
isNegative = true;
}
}
data = data.map(function(d, i) {
return [xValue.call(data, d, i), yValue.call(data, d, i)];
});
// Update the x-scale.
xScale
.domain(data.map(function(d) { return d[0];} ))
.rangeRoundBands([0, width - margin.left - margin.right], xRoundBands);
// Update the y-scale.
yScale
.domain(d3.extent(data.map(function(d) { return d[1];} )))
.range([height - margin.top - margin.bottom, 0])
.nice();
// Select the svg element, if it exists.
var svg = d3.select(this).selectAll("svg").data([data]);
// Otherwise, create the skeletal chart.
var gEnter = svg.enter().append("svg").append("g");
gEnter.append("g").attr("class", "bars");
gEnter.append("g").attr("class", "y axis");
gEnter.append("g").attr("class", "x axis");
gEnter.append("g").attr("class", "x axis zero");
// Update the outer dimensions.
svg .attr("width", width)
.attr("height", height);
// Update the inner dimensions.
var g = svg.select("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Update the bars.
var bar = svg.select(".bars").selectAll(".bar").data(data);
bar.enter().append("rect");
bar.exit().remove();
bar .attr("class", function(d, i) { return d[1] < 0 ? "bar negative" : "bar positive"; })
.attr("x", function(d) { return X(d); })
.attr("y", function(d, i) { return d[1] < 0 ? Y0() : Y(d); })
.attr("width", xScale.rangeBand())
.attr("height", function(d, i) { return Math.abs( Y(d) - Y0() ); });
// x axis at the bottom of the chart
if( isNegative === true ){
var xScaleHeight = height - margin.top - margin.bottom+12;
}else{
var xScaleHeight = height - margin.top - margin.bottom;
}
g.select(".x.axis")
.attr("transform", "translate(0," + ( xScaleHeight ) + ")")
.call(xAxis.orient("bottom"))
.selectAll("text")
.call(wrap, xScale.rangeBand());
// zero line
g.select(".x.axis.zero")
.attr("transform", "translate(0," + Y0() + ")")
.attr("class", "zero axis")
.call(xAxis.tickFormat("").tickSize(0));
// Update the text in bars.
var bar1 = svg.select(".bars").selectAll("text").data(data);
bar1 .data(data)
.enter()
.append("text")
.attr("class", "text")
.text(function(d) { return d[1]+"%"; })
.attr("x", function(d) { return X(d); })
.attr("y", function(d, i) { return d[1] < 0 ? Math.abs(Y(d)+10) : Y(d)-2; });
// Update the y-axis.
g.select(".y.axis")
//.call(yAxis)
.call(yAxis.tickSize(3).ticks(5))
.selectAll("g")
.selectAll("text")
.text(function(d){
return d+"%";
});
// Horizontal grid
g.insert("g", ".bars")
.attr("class", "grid horizontal")
.call(d3.svg.axis().scale(yScale)
.orient("left")
.tickSize(-(height), 0, 0)
.tickFormat("")
);
});
}
// Custom function for text wrap
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1, // ems
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > 55) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
// The x-accessor for the path generator; xScale ∘ xValue.
function X(d) {
return xScale(d[0]);
}
function Y0() {
return yScale(0);
}
// The x-accessor for the path generator; yScale ∘ yValue.
function Y(d) {
return yScale(d[1]);
}
chart.margin = function(_) {
if (!arguments.length) return margin;
margin = _;
return chart;
};
chart.width = function(_) {
if (!arguments.length) return width;
width = _;
return chart;
};
chart.height = function(_) {
if (!arguments.length) return height;
height = _;
return chart;
};
chart.x = function(_) {
if (!arguments.length) return xValue;
xValue = _;
return chart;
};
chart.y = function(_) {
if (!arguments.length) return yValue;
yValue = _;
return chart;
};
return chart;
}
You can do something like this
Instead of your code to generate horizontal grid
// Horizontal grid
g.insert("g", ".bars")
.attr("class", "grid horizontal")
.call(d3.svg.axis().scale(yScale)
.orient("left")
.tickSize(-(height), 0, 0)
.tickFormat("")
);
});
Add this it will iterate through the ticks .data(yScale.ticks(5))
g.append("g").selectAll("line.line").data(yScale.ticks(5)).enter()
.append("line")
.attr(
{
"class":"line grid tick",
"x1" : margin.right,
"x2" : width,
"y1" : function(d){ return yScale(d);},
"y2" : function(d){ return yScale(d);},
});
});
Working code here