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.
Related
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
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?
I'm working with D3 to create a line-graph. This graph is available here jsfiddle.
I'm trying to draw lines manually to represent certain data-point-values. I've tried to add comments to most of the lines in the code, so hopefully you can follow along.
My problem is that I cannot seem to draw negative numbers in a good way, if i do, then the graph-data-lines are misaligned. So my question is: How can i scale my graph so that I can show both negative and positive numbers? In this case, the graph should go from 2 to -2 based on the max/min values i've set.
currently. I'm scaling my graph like this
//
// Setup y scale
//
var y = d3.scale.linear()
.domain([0, max])
.range([height, 0]);
//
// Setup x scale
//
var x = d3.time.scale()
.domain(d3.extent(data, dateFn))
.range([0, width]);
In my mind, doing .domain([-2,max]) would be sufficient, but that seems to make things worse.
Also, my lines do not seem to match what the data-lines are saying. In the jsfiddle, the green line is set at 1. But the data-lines whose value are 1, are not on that green line.
So, this is pretty much a scale question i guess.
Visual (picasso-esc) representation of what the graph should look like if it worked.
As you want your y domain to be [-2, 2] as opposed to be driven by the data, you can remove a lot of setup and helper functions from your drawGraph function.
After drawing your graph, you can simply loop through the yLines array, and draw a line for each with the specified color, at the specified val according to your yScale.
Update: EDITED: As you will be supplied the values of nominal, upperTolerance, lowerTolerance, innerUpperTolerance, innerLowerTolerance from your endpoint (and they don't need to be calculated from the data on the client side), just feed those values into your data-driven yScale to draw the coloured lines.
Below I have just used the values 1, 1.8, -1.8, but you will receive values that will be more meaningfully tied to your data.
// Setup
const yLines = [{
val: 1,
color: 'green'
},
{
val: 1.8,
color: 'yellow'
},
{
val: -1.8,
color: 'red'
}
]
const margin = {
top: 10,
right: 80,
bottom: 60,
left: 20
};
const strokeWidth = 3;
const pointRadius = 4;
const svgWidth = 600;
const svgHeight = 600;
const width = svgWidth - margin.left - margin.right;
const height = svgHeight - margin.top - margin.bottom;
const stroke = '#2990ea'; // blue
const areaFill = 'rgba(41,144,234,0.1)'; // lighter blue
const format = d3.time.format("%b %e %Y");
const valueFn = function(d) {
return d.value
};
const dateFn = function(d) {
return format.parse(d.name)
};
// select the div and append svg to it
const graph = d3.select('#chart').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.style('overflow', 'visible');
const transformGroup = graph.append('g')
.attr('tranform', `translate(${margin.left}, ${margin.right})`)
// Make a group for yLines
const extraLines = transformGroup.append('g')
.attr('class', 'extra-lines')
// Generate some dummy data
const getData = function() {
let JSONData = [];
for (var i = 0; i < 30; i++) {
JSONData.push({
"name": moment().add(i, 'days').format('MMM D YYYY'),
"value": Math.floor(Math.random() * (Math.floor(Math.random() * 20))) - 10
})
}
return JSONData.slice()
}
const drawGraph = function(data) {
console.log(data)
// Setup y scale
const y = d3.scale.linear()
.domain(d3.extent(data.map((d) => d.value)))
.range([height, 0]);
// Setup y axis
const yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10)
.tickSize(0, 0, 0)
// append group & call yAxis
transformGroup.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + margin.left + ",0)")
.call(yAxis);
// Draw extra coloured lines from yLines array
extraLines.selectAll('.extra-line')
.data(yLines)
.enter()
.append('line')
.attr('class', 'extra-line')
.attr('x1', margin.left)
.attr('x2', svgWidth - margin.right)
.attr('stroke', d => d.color)
.attr('y1', d => y(+d.val))
.attr('y2', d => y(+d.val))
.attr('stroke-width', strokeWidth)
.attr('opacity', 0.5)
// Setup x scale
const x = d3.time.scale()
.domain(d3.extent(data, dateFn))
.range([0, width])
// function for filling area under chart
const area = d3.svg.area()
.x(d => x(format.parse(d.name)))
.y0(height)
.y1(d => y(d.value))
// function for drawing line
const line = d3.svg.line()
.x(d => x(format.parse(d.name)))
.y(d => y(d.value))
const lineStart = d3.svg.line()
.x(d => x(format.parse(d.name)))
.y(d => y(0))
// make the line
transformGroup.append('path')
.attr('stroke', stroke)
.attr('stroke-width', strokeWidth)
.attr('fill', 'none')
.attr('transform', `translate(${margin.left}, ${margin.top})`)
.attr('d', lineStart(data))
.attr('d', line(data))
// fill area under the graph
transformGroup.append("path")
.datum(data)
.attr("class", "area")
.attr('fill', areaFill)
.attr('transform', `translate(${margin.left}, ${margin.top})`)
.attr('d', lineStart(data))
.attr("d", area)
}
drawGraph(getData())
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<div id="chart" style="margin: 0 auto;"></div>
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.
In the example below, I'm trying to animate new items appearance.
As you can see, they animate from the bottom of the chart to their position.
However, existing items ("second" in this example) jump, instead of smoothly transitioning to their new position.
I thought it is because the new band suddenly appears, without a transition. So, I tried to add a transition:
const band = bandUpdate.enter()
.append('g')
.attr('class', 'band')
.merge(bandUpdate)
.transition(t)
.attr('transform', (_, i) => `translate(0, ${i * bandHeight})`);
But, I'm getting:
Uncaught TypeError: band.selectAll(...).data is not a function
Could you explain the error please, and suggest a way to avoid the undesired jump?
Bonus: How could I animate the y axis labels?
Playground
const width = 300;
const height = 200;
const margin = { top: 30, right: 30, bottom: 30, left: 50 };
let data = {};
const main = d3.select('.chart')
.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.scaleLinear().domain([0, 16]).range([0, width]);
const xAxis = d3.axisBottom(xScale);
main.append('g')
.attr('transform', `translate(0, ${height})`)
.call(xAxis);
const yScale = d3.scaleBand().domain([]).range([0, height]);
const yAxis = d3.axisLeft(yScale);
const yAxisG = main.append('g').call(yAxis);
const bandG = main.append('g');
function update() {
const t = d3.transition().duration(500);
const ids = Object.keys(data);
yScale.domain(ids);
yAxisG.call(yAxis);
const bandHeight = yScale.bandwidth();
const bandUpdate = bandG.selectAll('.band').data(ids, id => id);
const band = bandUpdate.enter()
.append('g')
.attr('class', 'band')
.merge(bandUpdate)
// .transition(t) // Throws: Uncaught TypeError: band.selectAll(...).data is not a function
.attr('transform', (_, i) => `translate(0, ${i * bandHeight})`);
bandUpdate.exit().remove();
const itemUpdate = band.selectAll('.item')
.data(id => data[id], item => item.value);
const itemG = itemUpdate.enter().append('g').attr('class', 'item');
const rectHeight = 4;
itemG
.append('rect')
.attr('class', (_, i) => `item-${i}`)
.attr('x', d => xScale(d.value))
.attr('width', d => width - xScale(d.value))
.attr('height', rectHeight)
.attr('y', height)
.transition(t)
.attr('y', bandHeight / 2 - rectHeight / 2);
itemG
.append('circle')
.attr('class', (_, i) => `item-${i}`)
.attr('cx', d => xScale(d.value))
.attr('r', 6)
.attr('cy', height)
.transition(t)
.attr('cy', bandHeight / 2);
itemUpdate
.select('rect')
.attr('x', d => xScale(d.value))
.attr('width', d => width - xScale(d.value))
.transition(t)
.attr('y', bandHeight / 2 - rectHeight / 2);
itemUpdate
.select('circle')
.attr('cx', d => xScale(d.value))
.transition(t)
.attr('cy', bandHeight / 2);
itemUpdate.exit().remove();
}
update();
setTimeout(() => {
data['first'] = [
{
value: 7
},
{
value: 10
}
];
update();
}, 1000);
setTimeout(() => {
data['second'] = [
{
value: 1
}
];
update();
}, 2000);
setTimeout(() => {
data['third'] = [
{
value: 13
}
];
update();
}, 3000);
svg {
margin: 0 30px 30px 30px;
}
.item-0 {
fill: red;
}
.item-1 {
fill: green;
}
<div class="chart"></div>
<script src="https://unpkg.com/d3#4.4.1/build/d3.js"></script>
Just break your band constant:
const band = bandUpdate.enter()
.append('g')
.attr('class', 'band')
.merge(bandUpdate);
band.transition(t)
.attr('transform', (_, i) => `translate(0, ${i * bandHeight})`);
Here is the updated CodePen: http://codepen.io/anon/pen/oBWJdp?editors=0010
Explanation:
According to the documentation, selection.transition([name]):
Returns a new transition on the given selection with the specified name.
So, when you later in the code do this:
const itemUpdate = band.selectAll('.item')
.data(id => data[id], item => item.value);
You're selecting a new transition, and that's giving you the error (you cannot bind data to a transition).
Breaking the band constant makes itemUpdate a selection based in the band selection, not in the following transition.