D3 linechart mouse move point position is wrong - d3.js

I want create a line chart, when I move mouse it'll show the point and this point data. This is my code. But I found the point position isn't correct. I have no idea. Thanks your help.
This is my code. I use d3 v4.
(function lineChart() {
elementId = 'real_monitor_platform_getBetAmount';
xMax = 1528170430000;
yMax = 8;
xMin = 1528170360000;
yMin = 0;
x = 'unixtime';
y = 'betAmount';
dataset = [{unixtime:1528170360000,betAmount:0},
{unixtime:1528170370000,betAmount:1},
{unixtime:1528170380000,betAmount:2},
{unixtime:1528170390000,betAmount:3},
{unixtime:1528170400000,betAmount:5},
{unixtime:1528170410000,betAmount:6},
{unixtime:1528170420000,betAmount:7},
{unixtime:1528170430000,betAmount:8}];
dataset.sort((a, b) => a[x] - b[x]);
const margin = {
top: 30, right: 40, bottom: 120, left: 60,
};
const w = 700;
const h = 300;
const width = w + margin.left + margin.right;
const height = h + margin.top + margin.bottom;
const formatTime = d3.timeFormat('%Y-%m-%d %H:%M:%S');
const svg = d3.select(`#${elementId}`).append('svg')
.attr('width', width)
.attr('height', height),
areaWidth = width - margin.left - margin.right,
areaHeight = svg.attr('height') - margin.top - margin.bottom,
g = svg.append('g')
.attr('id', 'group')
.attr('transform', `translate(${margin.left},${margin.top})`)
.attr('width', areaWidth)
.attr('height', areaHeight);
const xScale = d3.scaleTime()
.domain([xMin, xMax])
.range([0, areaWidth]);
const yScale = d3.scaleLinear().domain([yMin, yMax]).range([areaHeight, 0]);
// create axis objects
const xAxis = d3.axisBottom(xScale).ticks(width / 100);
const yAxis = d3.axisLeft(yScale);
const line = d3.line()
.x(d =>
xScale(d[x]),
).y(d =>
yScale(d[y]),
);
const t = d3.transition()
.duration(500)
.ease(d3.easeLinear);
const xGrooup = g.append('g')
.attr('transform', `translate(0,${areaHeight})`)
.call(xAxis.tickFormat(formatTime).ticks(3));
const yGroup = g.append('g')
.attr('transform', 'translate(0,0)')
.call(yAxis);
g.append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('width', areaWidth)
.attr('height', areaHeight);
const bisectDate = d3.bisector(d => d.unixtime).left;
const focus = g.append('g')
.attr('class', 'focus')
.style('display', 'none');
const circle = focus.append('circle')
.attr('r', 4)
.style('fill', '#F1F3F3')
.style('stroke', '#6F257F')
.style('stroke-width', '1px');
const updateLine = g.append('g')
.attr('class', 'chart')
.selectAll('line')
.data([dataset]);
const enterLine = updateLine.enter();
const exitLine = updateLine.exit();
const path = enterLine.append('path')
.attr('clip-path', 'url(#clip)')
.attr('class', 'line')
.attr('d', line)
.attr('fill', 'none')
.attr('stroke', 0)
.transition(t)
.attr('stroke-width', 1)
.attr('stroke', 'DodgerBlue');
exitLine.remove();
const zoom = d3.zoom()
.scaleExtent([1, 80])
.translateExtent([[0, 0], [areaWidth, areaHeight]])
.on('zoom', zoomed);
const zoomRect = svg.append('rect')
.attr('width', width)
.attr('height', height)
.attr('transform', 'translate(0,0)')
.style('fill', 'transparent')
.attr('fill', 'none')
.attr('pointer-events', 'all')
.call(zoom)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
function zoomed() {
const new_xScale = d3.event.transform.rescaleX(xScale);
xGrooup.call(xAxis.scale(new_xScale));
d3.select(`#${elementId}`).select('svg').select('#group').select('.chart')
.select('path.line')
.attr('d', line.x(d => new_xScale(d.unixtime)));
}
function mousemove() {
var transform = d3.zoomTransform(this);
var xt = transform.rescaleX(xScale);
var yt = transform.rescaleY(yScale);
let g = d3.select("#group")._groups[0][0]
var mouse = d3.mouse(g);
var x0 = xt.invert(mouse[0]);
var i = bisectDate(dataset, x0, 1);
var d0 = dataset[i - 1];
var d1 = dataset[i];
var d = x0 - d0[x] > d1[x] - x0 ? d1 : d0;
circle.attr("transform", `translate(${transform.applyX(xScale(d[x]))},${transform.applyY(yScale(d[y]))})`);
}
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="real_monitor_platform_getBetAmount"></div>
edit
This is work for me.
(function lineChart() {
elementId = 'real_monitor_platform_getBetAmount';
xMax = 1528170430000;
yMax = 8;
xMin = 1528170360000;
yMin = 0;
x = 'unixtime';
y = 'betAmount';
dataset = [{unixtime:1528170360000,betAmount:0},
{unixtime:1528170370000,betAmount:1},
{unixtime:1528170380000,betAmount:2},
{unixtime:1528170390000,betAmount:3},
{unixtime:1528170400000,betAmount:5},
{unixtime:1528170410000,betAmount:6},
{unixtime:1528170420000,betAmount:7},
{unixtime:1528170430000,betAmount:8}];
dataset.sort((a, b) => a[x] - b[x]);
const margin = {
top: 30, right: 40, bottom: 120, left: 60,
};
const w = 700;
const h = 300;
const width = w + margin.left + margin.right;
const height = h + margin.top + margin.bottom;
const formatTime = d3.timeFormat('%Y-%m-%d %H:%M:%S');
const svg = d3.select(`#${elementId}`).append('svg')
.attr('width', width)
.attr('height', height),
areaWidth = width - margin.left - margin.right,
areaHeight = svg.attr('height') - margin.top - margin.bottom,
g = svg.append('g')
.attr('id', 'group')
.attr('transform', `translate(${margin.left},${margin.top})`)
.attr('width', areaWidth)
.attr('height', areaHeight);
const xScale = d3.scaleTime()
.domain([xMin, xMax])
.range([0, areaWidth]);
const yScale = d3.scaleLinear().domain([yMin, yMax]).range([areaHeight, 0]);
// create axis objects
const xAxis = d3.axisBottom(xScale).ticks(width / 100);
const yAxis = d3.axisLeft(yScale);
const line = d3.line()
.x(d =>
xScale(d[x]),
).y(d =>
yScale(d[y]),
);
const t = d3.transition()
.duration(500)
.ease(d3.easeLinear);
const xGrooup = g.append('g')
.attr('transform', `translate(0,${areaHeight})`)
.call(xAxis.tickFormat(formatTime).ticks(3));
const yGroup = g.append('g')
.attr('transform', 'translate(0,0)')
.call(yAxis);
g.append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('width', areaWidth)
.attr('height', areaHeight);
const bisectDate = d3.bisector(d => d.unixtime).left;
const focus = g.append('g')
.attr('class', 'focus')
.style('display', 'none');
const circle = focus.append('circle')
.attr('r', 4)
.style('fill', '#F1F3F3')
.style('stroke', '#6F257F')
.style('stroke-width', '1px');
const updateLine = g.append('g')
.attr('class', 'chart')
.selectAll('line')
.data([dataset]);
const enterLine = updateLine.enter();
const exitLine = updateLine.exit();
const path = enterLine.append('path')
.attr('clip-path', 'url(#clip)')
.attr('class', 'line')
.attr('d', line)
.attr('fill', 'none')
.attr('stroke', 0)
.transition(t)
.attr('stroke-width', 1)
.attr('stroke', 'DodgerBlue');
exitLine.remove();
const zoom = d3.zoom()
.scaleExtent([1, 80])
.translateExtent([[0, 0], [areaWidth, areaHeight]])
.on('zoom', zoomed);
const zoomRect = svg.append('rect')
.attr('width', width)
.attr('height', height)
.attr('transform', 'translate(0,0)')
.style('fill', 'transparent')
.attr('fill', 'none')
.attr('pointer-events', 'all')
.call(zoom)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
function zoomed() {
const new_xScale = d3.event.transform.rescaleX(xScale);
xGrooup.call(xAxis.scale(new_xScale));
d3.select(`#${elementId}`).select('svg').select('#group').select('.chart')
.select('path.line')
.attr('d', line.x(d => new_xScale(d.unixtime)));
}
function mouseDate(scale) {
var g = d3.select("#group")._groups[0][0]
var x0 = scale.invert(d3.mouse(g)[0]),
i = bisectDate(dataset, x0, 1),
d0 = dataset[i - 1],
d1 = dataset[i],
d = x0 - d0[x] > d1[x] - x0 ? d1 : d0;
return d;
}
function mousemove() {
var transform = d3.zoomTransform(this);
var xt = transform.rescaleX(xScale);
var yt = transform.rescaleY(yScale);
d = mouseDate(xt);
console.log(transform.applyX(xScale(d[x])));
console.log(transform.applyY(yScale(d[y])));
circle.attr("transform", `translate(${transform.applyX(xScale(d[x]))},${(yScale(d[y]))})`);
}
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="real_monitor_platform_getBetAmount"></div>

You have to take the margins into account.
So, instead of...
const focus = svg.append('g')
... it should be:
const focus = g.append('g')
Here is the code with that change:
(function lineChart() {
elementId = 'real_monitor_platform_getBetAmount';
xMax = 1528170430000;
yMax = 8;
xMin = 1528170360000;
yMin = 0;
x = 'unixtime';
y = 'betAmount';
dataset = [{unixtime:1528170360000,betAmount:0},
{unixtime:1528170370000,betAmount:1},
{unixtime:1528170380000,betAmount:2},
{unixtime:1528170390000,betAmount:3},
{unixtime:1528170400000,betAmount:5},
{unixtime:1528170410000,betAmount:6},
{unixtime:1528170420000,betAmount:7},
{unixtime:1528170430000,betAmount:8}];
dataset.sort((a, b) => a[x] - b[x]);
const margin = {
top: 30, right: 40, bottom: 120, left: 60,
};
const w = 700;
const h = 300;
const width = w + margin.left + margin.right;
const height = h + margin.top + margin.bottom;
const formatTime = d3.timeFormat('%Y-%m-%d %H:%M:%S');
const svg = d3.select(`#${elementId}`).append('svg')
.attr('width', width)
.attr('height', height),
areaWidth = width - margin.left - margin.right,
areaHeight = svg.attr('height') - margin.top - margin.bottom,
g = svg.append('g')
.attr('id', 'group')
.attr('transform', `translate(${margin.left},${margin.top})`)
.attr('width', areaWidth)
.attr('height', areaHeight);
const xScale = d3.scaleTime()
.domain([xMin, xMax])
.range([0, areaWidth]);
const yScale = d3.scaleLinear().domain([yMin, yMax]).range([areaHeight, 0]);
// create axis objects
const xAxis = d3.axisBottom(xScale).ticks(width / 100);
const yAxis = d3.axisLeft(yScale);
const line = d3.line()
.x(d =>
xScale(d[x]),
).y(d =>
yScale(d[y]),
);
const t = d3.transition()
.duration(500)
.ease(d3.easeLinear);
const xGrooup = g.append('g')
.attr('transform', `translate(0,${areaHeight})`)
.call(xAxis.tickFormat(formatTime).ticks(3));
const yGroup = g.append('g')
.attr('transform', 'translate(0,0)')
.call(yAxis);
g.append('clipPath')
.attr('id', 'clip')
.append('rect')
.attr('width', areaWidth)
.attr('height', areaHeight);
const bisectDate = d3.bisector(d => d.unixtime).left;
const focus = g.append('g')
.attr('class', 'focus')
.style('display', 'none');
const circle = focus.append('circle')
.attr('r', 4)
.style('fill', '#F1F3F3')
.style('stroke', '#6F257F')
.style('stroke-width', '1px');
const updateLine = g.append('g')
.attr('class', 'chart')
.selectAll('line')
.data([dataset]);
const enterLine = updateLine.enter();
const exitLine = updateLine.exit();
const path = enterLine.append('path')
.attr('clip-path', 'url(#clip)')
.attr('class', 'line')
.attr('d', line)
.attr('fill', 'none')
.attr('stroke', 0)
.transition(t)
.attr('stroke-width', 1)
.attr('stroke', 'DodgerBlue');
exitLine.remove();
const zoom = d3.zoom()
.scaleExtent([1, 80])
.translateExtent([[0, 0], [areaWidth, areaHeight]])
.on('zoom', zoomed);
const zoomRect = svg.append('rect')
.attr('width', width)
.attr('height', height)
.attr('transform', 'translate(0,0)')
.style('fill', 'transparent')
.attr('fill', 'none')
.attr('pointer-events', 'all')
.call(zoom)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
function zoomed() {
const new_xScale = d3.event.transform.rescaleX(xScale);
xGrooup.call(xAxis.scale(new_xScale));
d3.select(`#${elementId}`).select('svg').select('#group').select('.chart')
.select('path.line')
.attr('d', line.x(d => new_xScale(d.unixtime)));
}
function mousemove() {
var mouse = d3.mouse(this);
var x0 = xScale.invert(mouse[0]);
var i = bisectDate(dataset, x0);
var d0 = dataset[i - 1];
var d1 = dataset[i];
var d = x0 - d0[x] > d1[x] - x0 ? d1 : d0;
circle.attr("transform", `translate(${xScale(d[x])},${(yScale(d[y]))})`);
}
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<div id="real_monitor_platform_getBetAmount"></div>
By the way, this won't work with the zoom... however, that's another issue.

Related

d.label is reading undefined on D3 Line chart

I'm quite new to d3 and found this tutorial from Ms. Urvashi Link Here
I am now able to see the plotted line chart however the mouse tooltip would cause an undefined error. It's undefined and unable to find the root cause of this, since the data is totally randomly generated even console.log() shows undefined.
So here's the error:
TypeError: Cannot read properties of undefined (reading 'label')
SVGRectElement.mousemove
http://localhost:3000/static/js/main.chunk.js:269:54
266 | const x0 = bisect(data, xScale.invert(xPos));
267 | const d0 = data[x0];
268 | console.log(data[x0]);
> 269 | focus.attr('transform', `translate(${xScale(d0.label)},${yScale(d0.value)})`);
| ^ 270 | tooltip.transition().duration(300).style('opacity', 0.9);
271 | tooltip.html(d0.tooltipContent || d0.label).style('transform', `translate(${xScale(d0.label) + 30}px,${yScale(d0.value) - 30}px)`);
272 | } // d3.select('#container')
I've been trying to figure out why the parameter d is undefined along with its attributes, however the graph renders except when hovering on the graph.
Here's the raw code:
import React, { useEffect } from 'react';
import './Linechart.scss';
import * as d3 from 'd3';
const Linechart = (props) => {
const { data, width, height } = props;
useEffect(() => {
drawChart();
}, [data])
const drawChart = () => {
//clears previous chart render
d3.select('#container')
.select('svg')
.remove();
d3.select('#container')
.select('.tooltip')
.remove();
const margin = { top: 50, right: 50, bottom: 50, left: 50 };
const yMinValue = d3.min(data, d => d.value);
const yMaxValue = d3.max(data, d => d.value);
const xMinValue = d3.min(data, d => d.label);
const xMaxValue = d3.max(data, d => d.label);
const svg = d3
.select('#container')
.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})`);
const xScale = d3 //x-axis
.scaleLinear()
.domain([xMinValue, xMaxValue])
.range([0, width]);
const yScale = d3 //y-axis
.scaleLinear()
.range([height, 0])
.domain([0, yMaxValue]);
const line = d3
.line()
.x(d => xScale(d.label))
.y(d => yScale(d.value))
.curve(d3.curveMonotoneX);
svg
.append('g')
.attr('class', 'grid')
.attr('transform', `translate(0,${height})`)
.call(
d3.axisBottom(xScale)
.tickSize(-height)
.tickFormat(''),
);
svg
.append('g')
.attr('class', 'grid')
.call(
d3.axisLeft(yScale)
.tickSize(-width)
.tickFormat(''),
);
svg
.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom().scale(xScale).tickSize(15));
svg
.append('g')
.attr('class', 'y-axis')
.call(d3.axisLeft(yScale));
svg
.append('path')
.datum(data)
.attr('fill', 'none')
.attr('stroke', '#f6c3d0')
.attr('stroke-width', 4)
.attr('class', 'line')
.attr('d', line);
const focus = svg
.append('g')
.attr('class', 'focus')
.style('display', 'none');
focus.append('circle').attr('r', 5).attr('class', 'circle');
const tooltip = d3
.select('#container')
.append('div')
.attr('class', 'tooltip')
.style('opacity', 0);
svg
.append('rect')
.attr('class', 'overlay')
.attr('width', width)
.attr('height', height)
.style('opacity', 0)
.on('mouseover', () => {
focus.style('display', null);
})
.on('mouseout', () => {
tooltip
.transition()
.duration(300)
.style('opacity', 0);
})
.on('mousemove', mousemove);
function mousemove(event) {
const bisect = d3.bisector(d => d.label).left;
const xPos = d3.pointer(this)[0];
const x0 = bisect(data, xScale.invert(xPos));
const d0 = data[x0];
console.log(data[x0]);
focus.attr(
'transform',
`translate(${xScale(d0.label)},${yScale(d0.value)})`,
);
tooltip
.transition()
.duration(300)
.style('opacity', 0.9);
tooltip
.html(d0.tooltipContent || d0.label)
.style(
'transform',
`translate(${xScale(d0.label) + 30}px,${yScale(d0.value) - 30}px)`,
);
}
}
return(
<div id="container"></div>
)
}
export default Linechart;
And the data is generated through this function:
const [data, setData] = useState([]);
const regenerateData = () => {
var chartData = [];
for (let index = 0; index < 20; index++) {
var value = Math.floor(Math.random() * index + 3);
var dataObject = {
label: index,
value,
tooltipContent:
`<b>x: </b> ${index} <br />
<b>y: </b> ${value}`,
}
chartData.push(dataObject);
}
setData(chartData);
}
If it is only a parameter, why is it needed to be defined? I'm quite puzzled about this, I am a bit of a slow learner so I'm sorry if I may have to ask a handful of questions.
Found it, Finally after countless console.log() I was able to figure it out then.
const xPos = d3.pointer(this)[0];
const x0 = bisect(data, xScale.invert(xPos));
Seems like this is an old method and I just happen to replace mouse with pointer to migrate however when I console.log(xPos), its just throwing undefined like why? It's just basically needed to be like this.
const xPos = d3.pointer(event);
const x0 = bisect(data, xScale.invert(xPos[0]));
const d0 = data[x0];
Now the tooltip circle shows up onhover with auto identify coordinates.

Bars not showing up in grouped bar chart

I'm trying to create a grouped bar chart showing students attenadance(present or absent) but for some reasons my chart is showing only one set of bars(the highest for present and the highest for absent. On inspecting the console I see that the remaining bars are there but they are not showing up on the chart. I don't know if the problem is with my data or my code(I have only included the js part of the code)
const SVG = {
height: 900,
width: 900
}
const margin = {top: 40, bottom: 40, right: 40, left: 40};
const innerWidth = SVG.width - margin.left - margin.right;
const innerHeight = SVG.height - margin.top - margin.bottom;
const bar = d3.select('body').append('svg').attr('width', SVG.width).attr('height', SVG.height);
const grp = bar.append('g').attr('transform', `translate(${margin.left}, ${margin.top})`);
const data = d3.csv('WEEK.csv')
.then(
function(data) {
console.log(data);
const keys = data.columns.slice(1);
console.log(keys)
const group = d3.map(data, function(d){return +d.Week}).keys()
const x0 = d3.scaleBand().domain(group)
.range([0, innerWidth]).padding(0.1)
const x1 = d3.scaleBand().domain(keys)
.range([0, x0.bandwidth()]).padding(0.05)
const y0 = d3.scaleLinear().domain([0, d3.max(data, function(d) {return d3.max(keys, function (key){return +d[key];}
)})])
.range([innerHeight, 0]);
const color = d3.scaleOrdinal().range('#fcba03', '#eb4034')
const yAxis = d3.axisLeft(y0);
const xAxis = d3.axisBottom(x0);
const yAxisG = grp.append('g').call(yAxis)
const xAxisG = grp.append('g').call(xAxis)
.attr('transform', `translate(0, ${innerHeight})`);
grp.append('g')
.selectAll('g')
.data(data)
.enter().append('g')
.selectAll('rect')
.attr("transform", function(d) {return "translate(" + x0(+d.Week) + ",0)"})
.data(function(d){return keys.map(function(key){return{key: key, value: +d[key]} }) })
.enter()
.append('rect')
.attr('width', x1.bandwidth())
.attr('height', d => (innerHeight - y0(d.value)))
.attr('y', d => y0(d.value))
.attr('x', d => x1(d.key))
.attr('fill', d => color(d.key));
})
The dataset:
Week ,Present,Absent
2,10481,4010
3,11277,4551
4,10499,5036
5,9970,5126
6,8901,4909
7,7929,8405
8,7995,5062
9,7785,5447
10,7670,5822
11,7177,6162
12,6258,6499
13,4689,6631

Multi-line graph does not matches the required result in d3js

I am new in d3.js.I made the multiline plot
Using this code file
import {
select,
csv,
scaleLinear,
scaleTime,
scaleOrdinal,
extent,
axisLeft,
scaleBand ,
axisBottom,
line,
curveBasis,
nest,
schemeCategory10,
timeFormat,
descending
} from 'd3';
import { colorLegend } from './colorLegend';
const svg = select('svg');
const width = +svg.attr('width');
const height = +svg.attr('height');
const render = data => {
const title='Profit Comparision by Segment by Region'
// Region,Sub_Category,Profit
const xValue = d => d.Sub_Category;
const xAxisLabel="Sub-Category"
const yValue = d => d.Profit;
const circleRadius = 6;
const yAxisLabel="Profit"
var barPadding = 0.2;
const colorValue = d => d.Region;
const margin = { top: 60, right: 160, bottom: 128, left: 105 };
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
/*const xScale = scaleLinear()
.domain(extent(data, xValue))
.range([0, innerWidth])
.nice();*/
var xScale = d3.scalePoint().domain(data.map(xValue))
.range([0, innerWidth]);
/* var xScale = scaleOrdinal().domain(extent(data, xValue))
.range([0, innerWidth]);*/
const yScale = scaleLinear()
.domain(extent(data, yValue))
.range([innerHeight, 0])
.nice();
const colorScale = scaleOrdinal(schemeCategory10);
const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
const xAxis = axisBottom(xScale)
.tickSize(-innerHeight)
.tickPadding(15);
const yAxis = axisLeft(yScale)
.tickSize(-innerWidth)
.tickPadding(10);
const yAxisG = g.append('g').call(yAxis);
yAxisG.selectAll('.domain').remove();
yAxisG.append('text')
.attr('class', 'axis-label')
.attr('y', -60)
.attr('x', -innerHeight / 2)
.attr('fill', 'black')
.attr('transform', `rotate(-90)`)
.attr('text-anchor', 'middle')
.text(yAxisLabel);
const xAxisG = g.append('g').call(xAxis)
.attr('transform', `translate(0,${innerHeight})`);
xAxisG
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", function(d) {
return "rotate(-65)"
});
xAxisG.select('.domain').remove();
xAxisG.append('text')
.attr('class', 'axis-label')
.attr('y', 110)
.attr('x', innerWidth / 2)
.attr('fill', 'black')
.text(xAxisLabel);
const lineGenerator = line()
.x(d => xScale(xValue(d)))
.y(d => yScale(yValue(d)))
.curve(curveBasis);
const lastYValue = d =>
yValue(d.values[d.values.length - 1]);
const nested = nest()
.key(colorValue)
.entries(data)
.sort((a, b) =>
descending(lastYValue(a), lastYValue(b))
);
console.log(nested);
colorScale.domain(nested.map(d => d.key));
g.selectAll('.line-path').data(nested)
.enter().append('path')
.attr('class', 'line-path')
.attr('d', d => lineGenerator(d.values))
.attr('stroke', d => colorScale(d.key));
g.append('text')
.attr('class', 'title')
.attr('y', -10)
.text(title);
svg.append('g')
.attr('transform', `translate(820,121)`)
.call(colorLegend, {
colorScale,
circleRadius: 10,
spacing: 38,
textOffset: 20
});
};
csv('data4.csv')
.then(data => {
data.forEach(d => {
// Region,Sub_Category,Profit
d.Profit = +d.Profit;
});
render(data);
});
This multiline plot does not match with the plot actual result on the same data. Here this picture in the actual result on the same data
How I can make this my multiline plot just like the result. What changes in the code is needed. Here is the link to vizhub code
Update:
I filtered and sorted them in alphabetical order and profit sorted in a descending order. But I got a different graph not the required result
How I can get the required result?

How to calculate the x and y coordinates of individual points within a radial line?

When creating a lineRadial, how can I get the x and y coordinates of the individual points of the line?
lineGenerator.angle() returns the angle of each point
lineGenerator.radius() returns the radius of each point
let width = 700;
let height = 500;
let svg = d3.select('svg')
.attr('width', width)
.attr('height', height);
let g = svg.append('g')
.attr('transform', `translate(${width/2}, ${height/2})`);
let randomY = d3.randomNormal(0,4);
let data = d3.range(20).map( (el,i) => { return {x:i, y:randomY()}; });
let r = 200;
let startAngle = -90 * (Math.PI / 180); // convert deg to rad
let endAngle = 90 * (Math.PI / 180); // convert deg to rad
let x = d3.scaleLinear()
.domain([ 0, 20 ])
.range([ startAngle, endAngle ]);
let y = d3.scaleLinear()
.domain([ 0,4 ])
.range([r - 10, r]);
let line = d3.lineRadial()
.angle( (d) => { return x(d.x); })
.radius( (d) => { return y(d.y); });
g.append('path')
.data([data])
.attr('class', 'line')
.attr('fill', 'none')
.attr('stroke', 'blue')
.attr('d', line);
g.selectAll('points')
.data(data)
.enter().append('circle')
.attr('fill', 'none')
.attr('stroke', 'black')
.attr('cx', line.angle() )
.attr('cy', line.radius() )
.attr('r', 2);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>
A little trigonometry...
let width = 700;
let height = 500;
let svg = d3.select('svg')
.attr('width', width)
.attr('height', height);
let g = svg.append('g')
.attr('transform', `translate(${width/2}, ${height/2})`);
let randomY = d3.randomNormal(0,4);
let data = d3.range(20).map( (el,i) => { return {x:i, y:randomY()}; });
let r = 200;
let startAngle = -90 * (Math.PI / 180); // convert deg to rad
let endAngle = 90 * (Math.PI / 180); // convert deg to rad
let x = d3.scaleLinear()
.domain([ 0, 20 ])
.range([ startAngle, endAngle ]);
let y = d3.scaleLinear()
.domain([ 0,4 ])
.range([r - 10, r]);
let line = d3.lineRadial()
// stash the angle and radius, while they are calculated
// angle must be rotated...
.angle( (d) => { d.a = x(d.x) - Math.PI/2; return x(d.x); })
.radius( (d) => { d.r = y(d.y); return y(d.y); });
g.append('path')
.data([data])
.attr('class', 'line')
.attr('fill', 'none')
.attr('stroke', 'blue')
.attr('d', line);
g.selectAll('points')
.data(data)
.enter().append('circle')
.attr('fill', 'none')
.attr('stroke', 'black')
.attr('transform', function(d){
// convert to cartesian
let p = {
x: (d.r * Math.cos(d.a)),
y: (d.r * Math.sin(d.a))
};
return 'translate(' + p.x + ',' + p.y + ')';
})
.attr('r', 2);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>

SVG path goes beyond chart area on d3 brush

When I try to brush & zoom a portion of the line chart, some parts of the selected area render outside the chart.
Code and behavior reproduction can be found at this jsbin.
Click & drag to select a portion and zoom in, double click to zoom out.
var svg = d3
.select('body')
.append('svg')
.attr('class', 'chart')
.attr('width', 960)
.attr('height', 500);
var margin = {
top: 40,
right: 40,
bottom: 40,
left: 40
};
var width = +svg.attr('width') - margin.left - margin.right;
var height = +svg.attr('height') - margin.top - margin.bottom;
var g = svg
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var timeParser = d3.timeParse('%Y-%m-%d');
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var brush = d3.brush().on('end', brushended);
var idleTimeout;
var idleDelay = 350;
var x0;
var y0;
var xAxis;
var yAxis;
var line = d3
.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.price);
})
.curve(d3.curveNatural);
var start = new Date();
var end = new Date(start.toDateString());
start.setFullYear(end.getFullYear() - 1);
var startStr = start.toISOString().slice(0, 10);
var endStr = end.toISOString().slice(0, 10);
var url = "https://api.coindesk.com/v1/bpi/historical/close.json?start=" + startStr + "&end=" + endStr;
d3.json(url, function(error, response) {
var data = Object.keys(response.bpi).map(function(date) {
return {
date: timeParser(date),
price: response.bpi[date]
};
});
x0 = d3.extent(data, function(d) {
return d.date;
});
y0 = d3.extent(data, function(d) {
return d.price;
});
x.domain(x0);
y.domain(y0);
xAxis = d3.axisBottom(x);
yAxis = d3.axisLeft(y);
g
.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
g
.append('g')
.attr('class', 'axis axis--y')
.call(yAxis);
g
.append('path')
.attr('class', 'line')
.datum(data)
.attr('fill', 'none')
.attr('stroke', 'steelblue')
.attr('d', line);
svg
.append('g')
.attr('class', 'brush')
.call(brush);
});
function brushended() {
var s = d3.event.selection;
if (!s) {
if (!idleTimeout) {
return (idleTimeout = setTimeout(idled, idleDelay));
}
x.domain(x0);
y.domain(y0);
} else {
x.domain([s[0][0] - 40, s[1][0] - 40].map(x.invert, x));
y.domain([s[1][1] - 40, s[0][1] - 40].map(y.invert, y));
svg.select('.brush').call(brush.move, null);
}
zoom();
}
function idled() {
idleTimeout = null;
}
function zoom() {
var t = svg.transition().duration(750);
svg
.select('.axis--x')
.transition(t)
.call(xAxis);
svg
.select('.axis--y')
.transition(t)
.call(yAxis);
svg
.select('.line')
.transition(t)
.attr('d', line);
}
.chart {
border: 1px solid #bdbdbd;
box-sizing: border-box;
}
<script src="https://unpkg.com/d3#4.12.2/build/d3.min.js"></script>
That's the expected behaviour. The most common way to deal with that is using a <clipPath>.
For instance, in your case:
var clipPath = g.append("defs")
.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
Then, in your path:
g.append('path')
//etc...
.attr("clip-path", "url(#clip)");
Here is the updated JSBin: https://jsbin.com/tatuhipevi/1/edit?js,output
And here the updated S.O. snippet:
var svg = d3
.select('body')
.append('svg')
.attr('class', 'chart')
.attr('width', 960)
.attr('height', 500);
var margin = {
top: 40,
right: 40,
bottom: 40,
left: 40
};
var width = +svg.attr('width') - margin.left - margin.right;
var height = +svg.attr('height') - margin.top - margin.bottom;
var g = svg
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
var clipPath = g.append("defs")
.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var timeParser = d3.timeParse('%Y-%m-%d');
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var brush = d3.brush().on('end', brushended);
var idleTimeout;
var idleDelay = 350;
var x0;
var y0;
var xAxis;
var yAxis;
var line = d3
.line()
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.price);
})
.curve(d3.curveNatural);
var start = new Date();
var end = new Date(start.toDateString());
start.setFullYear(end.getFullYear() - 1);
var startStr = start.toISOString().slice(0, 10);
var endStr = end.toISOString().slice(0, 10);
var url = "https://api.coindesk.com/v1/bpi/historical/close.json?start=" + startStr + "&end=" + endStr;
d3.json(url, function(error, response) {
var data = Object.keys(response.bpi).map(function(date) {
return {
date: timeParser(date),
price: response.bpi[date]
};
});
x0 = d3.extent(data, function(d) {
return d.date;
});
y0 = d3.extent(data, function(d) {
return d.price;
});
x.domain(x0);
y.domain(y0);
xAxis = d3.axisBottom(x);
yAxis = d3.axisLeft(y);
g
.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis);
g
.append('g')
.attr('class', 'axis axis--y')
.call(yAxis);
g
.append('path')
.attr('class', 'line')
.datum(data)
.attr('fill', 'none')
.attr('stroke', 'steelblue')
.attr('d', line)
.attr("clip-path", "url(#clip)");
svg
.append('g')
.attr('class', 'brush')
.call(brush);
});
function brushended() {
var s = d3.event.selection;
if (!s) {
if (!idleTimeout) {
return (idleTimeout = setTimeout(idled, idleDelay));
}
x.domain(x0);
y.domain(y0);
} else {
x.domain([s[0][0] - 40, s[1][0] - 40].map(x.invert, x));
y.domain([s[1][1] - 40, s[0][1] - 40].map(y.invert, y));
svg.select('.brush').call(brush.move, null);
}
zoom();
}
function idled() {
idleTimeout = null;
}
function zoom() {
var t = svg.transition().duration(750);
svg
.select('.axis--x')
.transition(t)
.call(xAxis);
svg
.select('.axis--y')
.transition(t)
.call(yAxis);
svg
.select('.line')
.transition(t)
.attr('d', line);
}
.chart {
border: 1px solid #bdbdbd;
box-sizing: border-box;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
Also, it's a good idea using a <clipPath> in the axes as well.

Resources