Can't append created SVG to div in D3 Vue3 - d3.js

here is my SFC
i commented on the part that works well and doesn't work as well
<template lang="pug">
.d3_simple
h1 My D3 Vue3 Example
svg(ref='svg_ref')
hr
div(ref='div_ref')
</template>
<script setup>
import { onMounted, ref } from 'vue'
import * as d3 from 'd3'
var svg_ref = $ref( null )
var div_ref = $ref( null )
// working
function d3_svg_ref()
{
d3.select( svg_ref )
.append( 'circle' )
.attr( 'cx', 50 )
.attr( 'cy', 50 )
.attr( 'r', 25 )
.style( 'fill', '#6fff00' )
}
// not working
function d3_div_ref()
{
var svg = d3.create( 'svg' )
.append( 'circle' )
.attr( 'cx', 50 )
.attr( 'cy', 50 )
.attr( 'r', 50 )
.style( 'fill', '#ffb300' )
d3.select( div_ref ).append( svg )
}
onMounted(
() =>
{
d3_svg_ref()
d3_div_ref()
}
)
</script>
getting an error Uncaught DOMException: String contains an invalid character
ps: keywords > len 3 - shouldn't exist -- please don't bother to notice it
It looks like your post is mostly code; please add some more details.
it's really silly, issue is pretty obvious without much talking ~_~

GPT-3 do the job
function gpt_fix1()
{
var svg = document.createElementNS( "http://www.w3.org/2000/svg", "svg" )
d3.select( svg )
.append( 'circle' )
.attr( 'cx', 50 )
.attr( 'cy', 50 )
.attr( 'r', 25 )
.style( 'fill', '#ffb300' )
d3.select( div_ref ).node().appendChild( svg )
}
function gpt_fix2()
{
var svg = d3.create( 'svg' )
svg.append( 'circle' ) // w/o "svg." not working
.attr( 'cx', 50 )
.attr( 'cy', 50 )
.attr( 'r', 25 )
.style( 'fill', '#4400ff' )
d3.select( div_ref ).html( svg.node().outerHTML )
}
function gpt_fix3()
{
var svg = d3.select( div_ref ).append( 'svg' )
svg.append( 'circle' ) // "svg." is a key
.attr( 'cx', 50 )
.attr( 'cy', 50 )
.attr( 'r', 25 )
.style( 'fill', '#00ccff' )
}

Related

d3 Change colour of tick on axis with function

I have an x axis in d3 with labels and circles for each tick. I want to change the colour of each dot with a function so that the colour comes from an array.
I have a function already that gets ticks from the array and positions them on the scale, but the same logic doesn't work for changing attributes of each circle.
I would like to be able to select each circle that is a child of the .tick class and change it's stroke attribute:
svg.selectAll(".tick", "circle")
.each(d => {
var dot = d3.select(d, "circle").attr("stroke", "red")
console.log("DOT", dot)
return dot
})
I would probably change the each to a proper for loop to iterate over both the data and circles arrays by matching indexes.
How would I make the circle colours correspond to those in the objects in the array 'data'?
import React, {useState, useEffect} from 'react';
import * as d3 from 'd3';
import './App.css';
function App() {
const [currentYear, setCurrentYear] = useState(2020);
const [maxYear, setMaxYear] = useState(30);
const [data, setData] = useState(
[
{x: 2020, colour: "purple", y1: 0.001, y2: 0.63},
{x: 2027, colour: "red", y1: 0.003, y2: 0.84},
{x: 2031, colour: "yellow", y1: 0.024, y2: 0.56},
{x: 2035, colour: "green", y1: 0.054, y2: 0.22},
{x: 2040, colour: "blue", y1: 0.062, y2: 0.15},
{x: 2050, colour: "orange", y1: 0.062, y2: 0.15}
]
);
const initialiseData = () => {
const svg = d3.select( "svg" );
const pxX = svg.attr( "width" );
const pxY = svg.attr( "height" );
const makeScale = ( accessor, range ) => {
console.log("RANGE", accessor, range)
return d3.scaleLinear()
.domain( d3.extent( data, accessor ) )
.range( range )
.nice()
}
const scX = makeScale( d => d["x"], [0, pxX - 50]);
const g = d3.axisBottom( scX ).tickValues(
data.map(d => {
return d.x
})
)
svg.append( "g" )
.attr( "transform", "translate(" + 25 + "," + pxY/2 + ")")
.call( g );
svg.selectAll(".domain").attr( "visibility", "hidden" );
svg.selectAll( ".tick" )
.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 5)
.attr("fill", "white")
.attr("stroke", "grey")
.attr("stroke-width", "4px")
svg.selectAll(".tick line")
.attr("visibility", "hidden")
svg.selectAll( ".tick text")
.attr("font-size", 20)
.attr("dy", "1.5em")
svg.selectAll(".tick", "circle")
.each(d => {
var dot = d3.select(d, "circle")
console.log("DOT", dot)
return
})
}
useEffect(() => {
if (data) {
initialiseData();
}
}, [data])
return (
<div className="App">
<svg id="demo1" width="1200" height="400" style={{background: "lightgrey"}}/>
</div>
);
}
export default App;
Your svg.selectAll( ".tick" ).append("circle") creates the circles. Using selectAll is a little like doing a for loop: it creates many elements, and each time, the data is bound to the created element.
You can provide a function to .attr() (and most other things in D3) that takes as an argument the bound data, usually written d. If you put in a selectAll, it'll be applied to each element.
See Learn D3: Joins for a more complete explanation. Putting it all together:
svg.selectAll( ".tick" )
.data(data)
.append("circle")
.attr("fill", d => d.colour) // d is an element of the data: get colour from it

D3 v5: how do I animate multiple lines to be "drawn" in a multiline chart?

Big thank you in advance, I am new to D3. I am trying to make a multiline chart that is animated, basically that acts as if the line is being "drawn" from left to right. I would like all the lines to be drawn at once.
Here is a link to the codepen project: https://codepen.io/amandaraen/project/editor/ZLLWBe
Basically, this is what I thought I should do based on Scott Murray's D3 book that I'm reading:
d3.transition().selectAll(".line")
.delay(function(d, i) { return i * 500; })
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d, i){
return colorScale(i);
});
But all this does is add the lines in their entirety one by one, instead, I would like for them to be unrolled. I have looked at lots of posts here and tried a lot of things but this is the closest I've come. Additionally, (and annoyingly) I had labels for this lines on the right hand side, but after I added the transition code the labels disappeared.
Edit, okay, I made a few changes and now, at least the labels have reappeared. So now it is only matter of how to make the lines appear like they are being drawn. Update:
var country = g.selectAll(".country")
.data(countries)
.enter().append("g")
.attr("class", "country");
// draw the lines
country.append("path")
.transition()
.duration(2000)
.delay(function(d, i) { return i * 500; } )
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
.style("stroke", function(d, i){
return colorScale(i);
});
EDIT:
Actually I would ignore my solution and try the stroke-dashed property described in this article
http://duspviz.mit.edu/d3-workshop/transitions-animation/
that I linked to through this question.
Animate line chart in d3
To make the line appear like it is drawn, use the attrTween along with the built in interpolator d3.interpolateNumber to set the x2, y2 values.
<!doctype html>
<html lang='en'>
<head>
<!-- Required meta tags -->
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'>
<!-- Bootstrap CSS -->
<link rel='stylesheet' href='https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css' integrity='sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh' crossorigin='anonymous'>
<title>Hello, world!</title>
</head>
<body>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src='https://code.jquery.com/jquery-3.4.1.min.js' integrity='sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=' crossorigin='anonymous'></script>
<script src='https://cdn.jsdelivr.net/npm/popper.js#1.16.0/dist/umd/popper.min.js' integrity='sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo' crossorigin='anonymous'></script>
<script src='https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js' integrity='sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6' crossorigin='anonymous'></script>
<!-- d3 v5 -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/5.15.0/d3.js'></script>
<script>
let width = 500
let height = 300
//make up some lines
let lines = [
{
"x1": 0,
"y1": 0,
"x2": 400,
"y2": 250
},
{
"x1": 0,
"y1": 250,
"x2": 400,
"y2": 0
}
]
//add lines
let svg = d3.select( "body" ).style( "padding", "30px" ).append( "svg" ).attr( "height", height).attr( "width", width )
// Here you set the x1, y1, x2, y2 values of line to all be equal to x1, y1,
// Then in your transition, use interpolate the number between x1 and x2 and y1
// and y2 respectively.
svg.selectAll( ".line").data( lines )
.enter().append( "line" )
.attr( "class", "line" )
.attr( "stroke-width", 2 )
.attr( "stroke", "black" )
.attr( "x1", d => d.x1 )
.attr( "y1", d => d.y1 )
.attr( "x2", d => d.x1 )
.attr( "y2", d => d.y1 )
.transition().duration( 750 )
.attrTween( "x2", d => {
return d3.interpolateNumber( d.x1, d.x2 )
})
.attrTween( "y2", d => {
return d3.interpolateNumber( d.y1, d.y2 )
})
</script>
</body>
</html>
Since this is a line it was easy to use the build in interpolator, but if it was a path or something you can define your own. I found this to be a great reference.
https://www.andyshora.com/tweening-shapes-paths-d3-js.html
EDIT: I realized you were making paths. I'm sure there is a more elegant solution, but you can break the path up at each node to create list of segments. For example, if the path was ABCD you would get
A
AB
ABC
ABCD
Then you can each line with a delay so A is drawn, then AB, then ABC, etc.
let width = 500
let height = 300
//get some paths
let paths = [
getPath( .5, 0 ),
getPath( .25, 0 )
]
// for each path, take a length from 0-i
// So for path [(0,0), (1, 1), (2,2)] =>
// [ [(0,0)], [(0,0),(1,1)], [(0,0), (1,1), (2,2)] ]
let pathSegments = paths.map( p => segmentPath( p ))
//make the line generator
var lineGenerator = d3.line()
.x(function(d, i) {
return d.x;
})
.y(function(d) {
return d.y;
});
//add the svg
let svg = d3.select("body").style("padding", "30px").append( "svg" ).attr( "height", height).attr( "width", width)
//add the group elements which will contain the segments for each line - merge with update selection -
let lines = svg.selectAll( ".line" ).data( pathSegments )
lines = lines.enter().append( "g" )
.attr( "class", "line" )
.merge( lines )
//To add line to g, selectAll to add the segments - data bound to lines will be passed
// down to children if data is called - in this case .data( d => d ) - d is the segmented
// path [ [(0,0)], [(0,0),(1,1)], [(0,0), (1,1), (2,2)] ] for a single line
// add a delay on stroke-width to give the appearance of being drawn
lines.selectAll( ".segment" ).data( d => d )
.enter().append( "path" )
.attr( "class", "segment" )
.attr( "stroke", "black" )
.attr( "stroke-width", 0 )
.attr( "fill", "none" )
.attr( "d", lineGenerator )
.transition().duration(750)
.delay( (d,i) => i * 100 )
.attr( "stroke-width", 2 )
//helper functions
function getPath( m, b ) {
let line = [...Array(5).keys()].map( d => {
return {
"x": d * 100,
"y": m * d * 100 + b,
}
})
return line
}
function segmentPath( path ) {
let segments = []
for( let i=1; i < path.length; i++ ) {
segments.push( path.slice(0, i ))
}
return segments
}
Of course, you might want to remove the extra segments after adding. Also, it only works if the path segments are small enough to make a smooth transition. If not, segment the line further, or break the path into a set of lines and use the x1,y1,x2,y2 interpolation from above.

How to make legend text responsive for D3 chart

I am trying to fix an issue in which there is a D3 donut chart with a legend located just to the right. The text of the legend keeps being cutoff. It's either visible outside of the container, or it's not displayed outside. Either way, it doesn't fit within the container, even though I can see that both the legend and the donut chart are part of the same SVG. You can see what I'm referring to in this image:
https://imgur.com/a/J3KrTA6
I am very new to working with D3, but I've been stuck on this issue for a while now. This isn't my code that I'm trying to fix, but here is where the options for generating the SVG are being passed in:
const donutOptions: DonutChartOptions = {
showPercentageInDonut: false,
width: 500,
height: 260,
title: {text: '', textStyle: {fontSize: '14px'}},
margin: {top: 120, right: 10, bottom: 65, left: 100},
colors: [Theme.Emerald.hex, Theme.Lime.hex, Theme.Teal.hex, Theme.SkyBlue.hex,
Theme.Marigold.hex, Theme.Azure.hex, Theme.Red.hex, Theme.Orange.hex]
};
const legendTextWrapWidthEdge = 1440;
const donutEthnicityOptions: DonutChartOptionsExtended = {
showPercentageInDonut: false,
width: 470,
height: 260,
title: {text: '', textStyle: {fontSize: '14px'}},
margin: {top: 120, right: 10, bottom: 65, left: 85},
colors: [Theme.Emerald.hex, Theme.Lime.hex, Theme.Teal.hex, Theme.SkyBlue.hex,
Theme.Marigold.hex, Theme.Azure.hex, Theme.Red.hex, Theme.Orange.hex],
legend: {textStyle: {fontSize: '14px'}},
legendOptions: {
legendRectVSpace: 10,
legendPositionX: 110,
legendPositionY: 85,
legendPercentagePositionX: 46,
legendPercentagePositionY: 15,
legendTextPositionX: 20,
legendTextWidth: (!!this.browserScreenWidth && this.browserScreenWidth < legendTextWrapWidthEdge) ? 100 : 200
}
};
I have tried experimenting with viewBox and preserveAspectRatio attributes, but I am apparently not doing something correctly.
This is the code that actually creates the chart using the aforementioned options. Messing with this code is kind of a last resort option if it can be avoided though. I think of it as a black box that I am providing merely for context:
import { DataSet } from '../data-set';
import { DonutChartOptionsExtended, ILegendOptions } from '../interfaces/DonutChartOptionsExtended';
import { XYChartSettings } from '../interfaces/XYChartSettings';
import { DEFAULTS } from '../interfaces/DEFAULTS';
import { TextStyle } from '../interfaces/TextStyle';
import Defaults from 'lodash-es/defaults';
import { getColorScale, initializeSvg, wrapText } from '../d3-fns';
export class DonutChart {
options: any;
dataset: any;
draw(dataSet?: DataSet, options?: DonutChartOptionsExtended) {
Promise.all([
import(/* webpackChunkName: "d3" */ 'd3-shape'),
import(/* webpackChunkName: "d3" */ 'd3-interpolate'),
import(/* webpackChunkName: "d3" */ 'd3-selection'),
import(/* webpackChunkName: "d3" */ 'd3-scale'),
import(/* webpackChunkName: "d3" */ 'd3-transition')
]).then(([d3Shape, d3Interpolate, d3Select, d3Scale, trans]) => {
if (dataSet) {
this.dataset = dataSet;
}
if (options) {
this.options = options;
}
const pie = d3Shape.pie()
.value((d: any) => d.value)
.sort(null)
.padAngle(.03);
const width = this.options.width;
const outerRadius = (width - 300) / 2;
const innerRadius = outerRadius / 2;
const arc = d3Shape.arc()
.outerRadius(outerRadius)
.innerRadius(innerRadius);
const settings: any = new XYChartSettings(this.options);
const svg = initializeSvg(d3Select, settings);
const color = getColorScale(d3Scale, settings.colors);
const dataRows = this.dataset.dataRows;
const path = svg.selectAll('path')
.data(pie(dataRows as any))
.enter()
.append('path')
.attr('d', (d: any, i: number, groups: any[]) => arc(d))
.attr('fill', (d: any, i: number, groups: any[]) => String(color(String(d.data.label))));
if (options && options.showPieAnimation) {
path.transition()
.duration(1000)
.attrTween('d', function (d: any) {
const interpolate = d3Interpolate.interpolate({startAngle: 0, endAngle: 0}, d);
return function (t: any) {
return arc(interpolate(t));
};
});
}
const restOfTheData = (mydata: any) => {
try {
const legendOptions: ILegendOptions = this.options.legendOptions;
const legendRectSize = !!legendOptions && legendOptions.legendRectHeight ? legendOptions.legendRectHeight : 20;
const legendSpacing = !!legendOptions && legendOptions.legendRectVSpace ? legendOptions.legendRectVSpace : 7;
const legendHeight = legendRectSize + legendSpacing;
const positionx = !!legendOptions && legendOptions.legendPositionX ? legendOptions.legendPositionX : 115;
const positiony = !!legendOptions && legendOptions.legendPositionY ? legendOptions.legendPositionY : 65;
if (options && options.showPercentageInDonut) {
this.displayPercentageOnThePie(mydata, svg, pie, arc);
}
const defaultColor = getColorScale(d3Scale, settings.colors);
if (this.options.colors) {
this.displayPercentageNextToLegend(
mydata, svg, defaultColor, positionx,
positiony, legendHeight,
settings.legend.textStyle.fontSize || '14px'
);
this.displayLengend(
d3Select, mydata, svg, defaultColor, legendHeight,
positionx, positiony, legendRectSize,
settings.legend.textStyle.fontSize || '14px'
);
} else {
this.displayPercentageNextToLegendDefault(
mydata, svg, positionx, positiony, legendHeight,
settings.legend.textStyle.fontSize || '14px'
);
this.displayLengendDefault(
svg, defaultColor, legendHeight,
positionx, positiony, legendRectSize,
settings.legend.textStyle.fontSize || '14px'
);
}
this.displayTitle(svg, settings);
} catch (ex) {
console.log(ex);
}
};
setTimeout(restOfTheData(dataRows), 1000);
})
}
private displayPercentageOnThePie(mydata: any, svg: any, pie: any, arc: any) {
svg.selectAll('text')
.data(pie(mydata))
.enter()
.append('text')
.transition()
.duration(200)
.attr('transform', (d: any, i: number, groups: any[]) => 'translate(' + arc.centroid(d) + ')')
.attr('dy', '.4em')
.attr('text-anchor', 'middle')
.text((d: any) => d.data.value + '%')
.style('fill', '#fff')
.style('font-size', '10px');
}
private displayPercentageNextToLegend(
mydata: any, svg: any, defaultColor: any, positionX: any,
positionY: any, legendHeight: any, fontSize: any) {
svg.selectAll('.percentage')
.data(mydata)
.enter().append('g')
.attr('class', 'percentage')
.attr('transform', (d: any, i: number, groups: any[]) => 'translate(' + (positionX + 40) +
',' + ((i * legendHeight) - positionY) + ')')
.append('text')
.style('fill', (d: any, i: number, groups: any[]) => defaultColor(i))
.style('text-anchor', 'end')
.style('font-size', fontSize)
.text((d: any) => d.value + '%');
}
private displayPercentageNextToLegendDefault(mydata: any, svg: any, positionX: any, positionY: any, legendHeight: any, fontSize: any) {
svg.selectAll('.percentage')
.data(mydata)
.enter().append('g')
.attr('class', 'percentage')
.attr('transform', (d: any, i: number, groups: any[]) => 'translate(' + (positionX + 40) +
',' + ((i * legendHeight) - positionY) + ')')
.append('text')
.style('fill', '#000')
.style('text-anchor', 'end')
.style('font-size', fontSize)
.text((d: any) => d.value + '%');
}
private displayLengend(d3Select: any, mydata: any, svg: any, defaultColor: any, legendHeight: any,
positionX: any, positionY: any, legendRectSize: any, fontSize: any) {
const legendOptions: ILegendOptions = this.options.legendOptions;
const legendRectWidth = !!legendOptions && legendOptions.legendRectWidth ? legendOptions.legendRectWidth : 10;
const percentageOffsetX = !!legendOptions && legendOptions.legendPercentagePositionX ? legendOptions.legendPercentagePositionX : 56;
const percentageOffsetY = !!legendOptions && legendOptions.legendPercentagePositionY ? legendOptions.legendPercentagePositionY : 15;
const textOffsetX = !!legendOptions && legendOptions.legendTextPositionX ? legendOptions.legendTextPositionX : 30;
const textOffsetY = !!legendOptions && legendOptions.legendTextPositionY ? legendOptions.legendTextPositionY : 15;
const textWidth = !!legendOptions && legendOptions.legendTextWidth ? legendOptions.legendTextWidth : 200;
const legend = svg.selectAll('.legend')
.data(mydata)
.enter()
.append('g')
.attr('class', 'legend')
// Just a calculation for x & y position
.attr('transform',
(d: any, i: number, groups: any[]) => `translate(${positionX
+ percentageOffsetX},${(i * legendHeight) - (positionY + percentageOffsetY)})`);
legend.append('rect')
.attr('width', legendRectWidth)
.attr('height', legendRectSize)
.attr('rx', 1)
.attr('ry', 1)
.style('fill', (d: any, i: number, groups: any[]) => defaultColor(i))
.style('stroke', (d: any, i: number, groups: any[]) => defaultColor(i));
legend.append('text')
.attr('x', textOffsetX)
.attr('y', textOffsetY)
.text((d: any) => d.label)
.style('fill', '#000')
.style('font-size', fontSize)
.call(wrapText, d3Select, textWidth);
}
private displayLengendDefault(svg: any, defaultColor: any, legendHeight: any,
positionX: any, positionY: any, legendRectSize: any, fontSize: any) {
const legendRectWidth = 10;
const legend = svg.selectAll('.legend')
.data(defaultColor.domain())
.enter()
.append('g')
.attr('class', 'legend')
// Just a calculation for x & y position
.attr('transform', (d: any, i: number, groups: any[]) => 'translate(' + (positionX + 50) +
',' + ((i * legendHeight) - (positionY + 15)) + ')');
legend.append('rect')
.attr('width', legendRectWidth)
.attr('height', legendRectSize)
.attr('rx', 1)
.attr('ry', 1)
.style('fill', defaultColor)
.style('stroke', defaultColor);
legend.append('text')
.attr('x', 30)
.attr('y', 15)
.text((d: any) => d)
.style('fill', '#929DAF')
.style('font-size', fontSize);
}
private displayTitle(svg: any, settings: any) {
const textStyle = <TextStyle>Defaults(settings.title.textStyle || {}, DEFAULTS.textStyleTitle);
svg.append('text')
.attr('x', settings.widthInner / 2)
.attr('y', 0 - (settings.margin.top / 1.15 ))
.attr('text-anchor', 'middle')
.style('font-size', textStyle.fontSize)
.style('text-decoration', textStyle.textDecoration)
.text(settings.title.text);
}
}

Different color between line and area graph

edit image
I am using d3 v4. In which I created area and line chart. data formation is.
Chart => [1,4,5,2.4,6]
line => [3,3,3,3,3]
Now my requirement is below the line chart, area graph should have a different color.
If there is any other library which can do this, then also suggest.
Here is my function to draw the line and area graph.
so if dailyConsValue> targetConsValue ? 'no color' : 'show color'
dailydate is year [Jan 2017 - dec 2017] => this is dynamically.
Below is my code:
function drawGraph(drawData, highlightedData, first, last) {
let margin, width, height, parseDate, xScale, yScale, area,
dailyConsumption, targetConsumption, svg;
margin = {
top: 10,
right: 60,
bottom: 10,
left: 60
},
width = document.querySelector('#graph').clientWidth - margin.left - margin.right,
height = document.querySelector('#graph').clientHeight - margin.top - margin.bottom;
parseDate = d3.timeParse('%b %Y');
// x axis
xScale = d3.scaleTime().domain([parseDate(first), parseDate(last)]).range([0, width]);
// y axis
yScale = d3.scaleLinear().domain([-1, 2 + Math.max.apply(this, drawData.map(function(o){return o.dailyConsValue; }))])
.range([height, 0]);
area = d3.area()
.x(function(d) { return xScale(d['dailyDate']); })
.y0(function(d) { return yScale(d['targetConsValue']); })
.y1(function(d) { return yScale(d['dailyConsValue']); });
// define the line
dailyConsumption = d3.line().x(function(d) {return xScale(d['dailyDate']); })
.y(function(d) {return yScale(d['dailyConsValue']); });
targetConsumption = d3.line().x(function(d) {return xScale(d['dailyDate']); })
.y(function(d) {return yScale(d['targetConsValue']); });
svg = d3.select('#graph').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 + ')');
// add axis
svg.append('g')
.attr('transform', 'translate(0,0)').attr('class', 'Xaxis')
.call(d3.axisTop(xScale).ticks(drawData.length).tickSize(-innerHeight).tickFormat(d3.timeFormat('%b %Y')));
svg.append('g').attr('class', 'Yaxis')
.call(d3.axisLeft(yScale).tickSize(0));
// Prepare data
drawData.forEach(function( d ) {
d['dailyDate'] = parseDate( d['dailyDate'] );
d['targetConsValue'] = d['targetConsValue'];
d['dailyConsValue'] = d['dailyConsValue'];
} );
highlightedData.forEach(function( d ) {
d['dailyDate'] = parseDate( d['dailyDate'] );
d['dailyConsValue'] = d['dailyConsValue'];
d['active'] = d['active'];
} );
// add the area
svg.append('path').data([drawData]).attr('class', 'area')
.attr('d', area)
.transition().duration(2500).attrTween( 'd', this.tween( drawData, area ) );
// add data for first line
svg.append('path')
.data([drawData])
.attr('class', 'daily').attr('d', targetConsumption).transition()
.duration(2500).delay(1500 / 2).attrTween( 'd', this.tween( drawData, targetConsumption ) );
// add data for futureConsumption
svg.append('path')
.data([drawData])
.attr('class', 'target').attr('data-legend', 'CST')
.attr('d', dailyConsumption);
createLengends(drawData);
drawCircle(drawData, '10', '#037DA6');
drawCircle(drawData, '5', '#003A54');
/**
* legends start
* #param datalegendsData
*/
function createLengends(datalegendsData) {
let legend;
legend = svg.selectAll('.legend').data([datalegendsData])
.enter().append('g').attr('class', 'legend')
.attr('transform', function(d, i) { return 'translate(0,' + i * 20 + ')'; });
// making a line for legend
legend.append('line')
.attr('x1', width - 335).attr('x2', width - 305).attr('y1', height - 1)
.attr('y2', height - 1).style('stroke', '5,5').style('stroke', '#4682B4');
legend.append('text').attr('x', width - 300).attr('y', height - 1)
.attr('dy', '.35em').style('text-anchor', 'start').text('IST Verbrauch WOB')
.style('fill', '#666').style('font-weight', 'normal');
legend.append('line').attr('x1', width - 130).attr('x2', width - 100)
.attr('y1', height - 1).attr('y2', height - 1).style('stroke-dasharray', '5,5')
.style('stroke', '#003A54');
legend.append('text').attr('x', width - 96).attr('y', height - 1).attr('dy', '.35em')
.style('text-anchor', 'start').text('CST Sollwert').style('fill', '#666')
.style('font-weight', 'normal');
}
// legends end
/**
* highlighted data points start
* #param data
* #param r
* #param color
*/
function drawCircle(data, r, color) {
let tooltip;
tooltip = d3.select('#graph').append('div')
.attr('class', 'tooltip').style('display', 'none');
svg.selectAll('.dot').data(data).enter().append('circle') // Uses the enter().append() method
.attr('class', 'circle_' + r).attr('cx', function(d) { return xScale(d['dailyDate']); })
.attr('cy', function(d) {return yScale(d['dailyConsValue']); }).attr('r', r).attr('fill', color)
.on( 'mouseover', function( d) {
let arr, date;
d3.select( this ).classed('circle circle__highlighted', true);
d['active'] = true;
tooltip.transition().duration(200).style('display', 'block');
arr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
date = arr[d['dailyDate'].getMonth()] + ', ' + d['dailyDate'].getFullYear();
tooltip.html('Month ' + date + '<br/> Consumption- ' + d['dailyConsValue'] + ' unit')
.style('left', (parseInt(this.getAttribute('cx'), 10) + 70) + 'px').style('top', (this.getAttribute('cy')) + 'px');
/*tooltip.html('Month ' + date + '<br/> Consumption- ' + d['dailyConsValue'] + ' unit')
.style('left', (d3.event.offsetX + 20) + 'px').style('top', (d3.event.offsetY - 28) + 'px');*/
} ).on( 'mouseout', function( d ) {
d3.selectAll('.circle').classed('circle', true).classed('circle__highlighted', false);
d['active'] = false;
tooltip.transition().duration(500).style('display', 'none');
} );
}
// highlighted data points end
}
Thanks in advance.
In your style fill just create a function and compare the two values and return a color based on the condition like so:
svg.append('path')
.data([drawData]).attr('class', 'area')
.attr('d', area)
.style('fill', function(d){
for (i in d) {
if (d[i]['dailyConsValue'] < d[i]['targetConsValue']) {
return "green"
} else {
return "blue"
}
}
})
.transition().duration(2500)
.attrTween( 'd', this.tween( drawData, area ) );
This will work based on only the last value of the data. If you want different colors at every point of the chart depending on your condition, you will have to create a linearGradient with multiple color stops (dynamically) as far as I know.
Now to create a linearGradient, I have created a linear scale using d3 to calculate the offset for color stops and assign color based on your condition. To dynamically create a linearGradient refer to this fiddle
https://jsfiddle.net/aditya_kk29/gsy5dt8h/

D3.JS Animating the bars in a bar chart

I am trying to create a bar char which animates the bars to load one after the other. I have got this far(shown in JSFiddle), but the transitions are not smooth. Please let me know what changes are to be done to make the animations more smooth.
CSS:
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: #41a2d3;
}
.x.axis path {
display: none;
}
.tipsy-inner {
text-align: left;
}
.tipsy-inner a {
color: white;
}
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
}
.grid path {
stroke-width: 0;
}
SCRIPT:
var jsonData = [{"dist":"NE_BLR1","bscore":93.707634,"cscore":88.206},{"dist":"NE_BLR2","bscore":2.9553592,"cscore":28.037703},{"dist":"NE_BLR3","bscore":6.6249013,"cscore":60.771496},{"dist":"NE_BLR4","bscore":90.87391,"cscore":86.42939},{"dist":"NE_BLR5","bscore":81.874275,"cscore":1.5155494},{"dist":"NE_BLR6","bscore":11.794424,"cscore":11.316788},{"dist":"NE_BLR7","bscore":42.103214,"cscore":42.328335},{"dist":"NE_BLR8","bscore":96.59683,"cscore":2.5692165},{"dist":"NE_BLR9","bscore":80.03519,"cscore":34.86772},{"dist":"NE_BLR10","bscore":67.834175,"cscore":39.31076},{"dist":"NE_BLR11","bscore":26.525862,"cscore":18.048836},{"dist":"NE_BLR12","bscore":14.0657425,"cscore":29.272057},{"dist":"NE_BLR13","bscore":20.054913,"cscore":53.572613},{"dist":"NE_BLR14","bscore":11.77302,"cscore":66.45609},{"dist":"NE_BLR15","bscore":81.25657,"cscore":77.80638},{"dist":"NE_BLR16","bscore":47.63029,"cscore":1.1937499},{"dist":"NE_BLR17","bscore":79.54459,"cscore":81.614},{"dist":"NE_BLR18","bscore":94.08074,"cscore":80.3455},{"dist":"NE_BLR19","bscore":55.68032,"cscore":89.06907},{"dist":"NE_BLR20","bscore":45.28445,"cscore":45.069145},{"dist":"NE_BLR21","bscore":82.41166,"cscore":59.125107},{"dist":"NE_BLR22","bscore":33.733047,"cscore":93.37988},{"dist":"NE_BLR23","bscore":98.53198,"cscore":18.078703},{"dist":"NE_BLR24","bscore":83.15471,"cscore":40.400578},{"dist":"NE_BLR25","bscore":3.6420703,"cscore":45.9239},{"dist":"NE_BLR26","bscore":56.563927,"cscore":83.02267},{"dist":"NE_BLR27","bscore":10.62563,"cscore":76.39983},{"dist":"NE_BLR28","bscore":83.05605,"cscore":91.5188},{"dist":"NE_BLR29","bscore":99.7658,"cscore":32.68316},{"dist":"NE_BLR30","bscore":79.63283,"cscore":78.877335},{"dist":"NE_BLR31","bscore":27.242237,"cscore":51.338135},{"dist":"NE_BLR32","bscore":69.210144,"cscore":11.239213},{"dist":"NE_BLR33","bscore":6.4760566,"cscore":53.43964},{"dist":"NE_BLR34","bscore":60.054676,"cscore":18.344189},{"dist":"NE_BLR35","bscore":93.7649,"cscore":99.91859},{"dist":"NE_BLR36","bscore":30.083233,"cscore":91.4337},{"dist":"NE_BLR37","bscore":80.51691,"cscore":28.400452},{"dist":"NE_BLR38","bscore":19.416237,"cscore":44.272415},{"dist":"NE_BLR39","bscore":79.10841,"cscore":60.43038},{"dist":"NE_BLR40","bscore":1.789844,"cscore":89.061325},{"dist":"NE_BLR41","bscore":14.223904,"cscore":4.383576},{"dist":"NE_BLR42","bscore":88.20337,"cscore":97.80883},{"dist":"NE_BLR43","bscore":18.071491,"cscore":62.987053},{"dist":"NE_BLR44","bscore":30.62138,"cscore":87.54846},{"dist":"NE_BLR45","bscore":17.996502,"cscore":1.733619},{"dist":"NE_BLR46","bscore":88.58935,"cscore":67.55461},{"dist":"NE_BLR47","bscore":85.947365,"cscore":1.1164486},{"dist":"NE_BLR48","bscore":3.997153,"cscore":2.8819382},{"dist":"NE_BLR49","bscore":48.500816,"cscore":21.182537},{"dist":"NE_BLR50","bscore":88.485214,"cscore":92.17681}];
var results,
data = [],
chart,
bars,
tip,
margin = 100,
w = 15,
h = 500,
count= 0,
x, y,
xAxis, yAxis;
results = d3.map( jsonData );
results.forEach( function( key, val ) {
var result = {};
count = count+1;
result.count = parseInt(count);
result.bscore = parseFloat( val.bscore );
result.cscore = parseFloat( val.cscore );
result.dist = val.dist;
data.push( result );
} );
chart = d3.select("body").append( 'svg:svg' )
.attr( 'class', 'chart' )
.attr( 'width', 1300 )
.attr( 'height', h )
.append('g');
d3.select('svg g')
.attr('transform', 'translate(50, 50)');
x = d3.scale.linear()
.domain( [0, data.length] )
.range( [0, 960] );
y = d3.scale.linear()
.domain( [0, d3.max( data, function( d ) { return d.bscore; } )] )
.rangeRound( [h - margin, 0] );
chart.append("g")
.attr("class", "grid")
.call(d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10)
.tickSize(-1000, 0, 0)
.tickFormat("")
)
.attr("stroke-width", "0.2px");
// Bars
bars = chart.append('g')
.attr('class', 'bars');
// Axis
xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
yAxis = d3.svg.axis()
.scale(y)
.orient("left");
chart.append('g')
.attr('class', 'x axis')
.attr('transform', 'translate(0, ' + (h) + ')')
.call(xAxis);
chart.append('g')
.attr('class', 'y axis')
.attr('transform', 'translate(' + x.range()[0] + ')')
.call(yAxis);
bars.selectAll( 'rect' )
.data( data )
.enter()
.append( 'svg:rect' )
.attr('class', 'bar')
.attr( 'x', function( d ) { return x( d.count );} )
.attr( 'width', w )
.transition()
.delay(function (d,i){ return i * 300;})
.duration(250)
.attr("y", function(d) { return y( d.bscore ); })
.attr( 'height', function( d ) { return (h - margin) - y( d.bscore ); } );
JSFiddle: http://jsfiddle.net/pg77k/8/
TYIA
You need to initialise the values of y and height to what you want them to be at the start of the transition:
.attr( 'height', 0 )
.attr("y", h - margin)
Complete demo here.

Resources