I'm not sure I understand how to use D3's interpolateSpectral to get my color scheme. When I attempt the following my colors work, the problem is I don't know how many potential colors I could need so I wanted to use interpolateSpectral to get as many as my dataset asks for.
When I do this
const color = d3.scaleOrdinal(d3.schemeCategory10);
g.selectAll('.chart-arc')
.data(pie(data))
.enter()
.append('path')
.attr('class', 'chart-arc')
.attr('d', arc)
.style('fill', d => console.log(color(d.data.label)))
.on('mouseover', this.mouseover.bind(this))
.on('mousemove', this.mousemove.bind(this))
.on('mouseout', this.mouseout.bind(this))
;
Those colors work as I can see them logged to my console.
When I do this...
const color = d3.scaleSequential(d3.interpolateSpectral);
g.selectAll('.chart-arc')
.data(pie(data))
.enter()
.append('path')
.attr('class', 'chart-arc')
.attr('d', arc)
.style('fill', d => console.log(color(d.data.label)))
.on('mouseover', this.mouseover.bind(this))
.on('mousemove', this.mousemove.bind(this))
.on('mouseout', this.mouseout.bind(this))
;
The the console logs all instances of color as undefined.
UPDATE
This is the full code for the vue.js/d3js page.
<template>
<div>
<div v-if="!loading" id="chart"></div>
<div v-if="loading">Loading...</div>
</div>
</template>
<script>
import * as d3 from 'd3';
import axios from "axios";
export default {
name: "piechart2",
data(){
this.loadData()
.then((theData) => (
this.data = theData,
this.createChart
))
.finally(() => (
this.loading = false,
this.loadChart()
));
return{
data: "",
element: 'body',
width: 600,
height: 400,
loading: true
}
},
methods: {
loadData: function(){
return axios.get('http://localhost:8080/mockdata/piemock.json')
.then(result => { return result; })
.catch(error => { console.error(error); throw error; });
},
loadChart: function () {
let newData = this.data.data.map(
obj => {
return {
value: obj.count,
label : obj.label
}
}
);
let newJson = {data: newData, width: this.width, height: this.height, element: this.element}
var {data, width, height, element} = newJson;
const svg = d3.select(element)
.append('svg')
.attr('class', 'chart-svg')
.attr('width', width)
.attr('height', height)
;
this.tooltip = d3.select(element)
.append('div')
.attr('class', 'tooltip')
.style('display', 'none')
;
// const color = d3.scaleOrdinal(d3.schemeCategory10)
// ;
// console.log(color(1))
console.log(data.length)
const color = d3.scaleSequential(d3.interpolateSpectral).domain([0, data.length]);//d3.scaleSequential(d3.interpolateSpectral);
// console.log(color2(1))
const r = Math.min(width, height) / 3;
const arc = d3.arc()
.innerRadius(0)
.outerRadius(r)
;
const pie = d3.pie()
.value(d => d.value)
;
const g = svg.append('g')
.attr('transform', `translate(${width / 2},${height / 2})`)
;
g.selectAll('.chart-arc')
.data(pie(data))
.enter()
.append('path')
.attr('class', 'chart-arc')
.attr('d', arc)
.style('fill', d => console.log(color(d.data.label)))
.on('mouseover', this.mouseover.bind(this))
.on('mousemove', this.mousemove.bind(this))
.on('mouseout', this.mouseout.bind(this))
;
const l = svg.append('g')
.attr('transform', `translate(0,${height - 20})`);
const xl = d3.scaleBand()
.range([0, width])
.padding(0.3)
.domain(data.map(d => d.label))
;
const legend = l.selectAll('.chart-legend')
.data(color.domain())
.enter()
.append('g')
.attr('class', 'chart-legend')
.attr('transform', (d) => `translate(${xl(d)},0)`)
;
legend.append('rect')
.attr('width', 12)
.attr('height', 12)
.style('fill', color)
;
legend.append('text')
.attr('x', 20)
.attr('y', 10)
.text(d => d)
;
},
mouseover() {
this.tooltip
.style('display', 'inline-block')
.style('position', 'absolute')
;
},
mousemove() {
this.tooltip
.text([d3.event.pageX, d3.event.pageY].join(','))
.style('left', d3.event.pageX + 10 + "px")
.style('top', d3.event.pageY + 10 + "px")
;
},
mouseout() {
this.tooltip
.style('display', 'none')
;
},
render() {
// move rendering logic down here
}
}
}
</script>
<style>
.tooltip {
background-color: rgba(0,0,0,0.75);
padding: 15px;
border-radius: 2px;
font-family: sans-serif;
color: white;
pointer-events: none;
box-shadow: 0 0 5px #999999;
}
.chart-svg {
border: 1px solid #ddd;
}
.chart-legend {
font-family: 'Open Sans', sans-serif;
}
</style>
Here is the JSON
[{"label": "Assamese", "count": 13},
{"label": "Bengali", "count": 83},
{"label": "Bodo", "count": 1.4},
{"label": "Dogri", "count": 2.3},
{"label": "Gujarati", "count": 46},
{"label": "Hindi", "count": 300},
{"label": "Kannada", "count": 38},
{"label": "Kashmiri", "count": 5.5},
{"label": "Konkani", "count": 5},
{"label": "Maithili", "count": 20},
{"label": "Malayalam", "count": 33},
{"label": "Manipuri", "count": 1.5},
{"label": "Marathi", "count": 73},
{"label": "Nepali", "count": 2.9},
{"label": "Oriya", "count": 33},
{"label": "Punjabi", "count": 29},
{"label": "Sanskrit", "count": 0.01},
{"label": "Santhali", "count": 6.5},
{"label": "Sindhi", "count": 2.5},
{"label": "Tamil", "count": 61},
{"label": "Telugu", "count": 74},
{"label": "Urdu", "count": 52}]
Sequential scales do take exactly two numeric values as input domain (source).
This means that the input should be a number, and output a color.
It seems that in the question, an attempt is made to use text labels as input, this is causing the problem.
It is not recommended to use a sequential color scale to map unrelated value: The color scale denotes "proximity" between values, and will be used on labels which are not necessarily connected / similar.
In case the 12 colors categorical scale schemeSet3 from d3-scale-chromatic is not enough, a scale can be generated with a tool like iWantHue, optimizing the chances of having differentiable colors.
If it is decided to stay with the approach of slicing colors from d3.interpolateSpectral, then this notebook can be used as a reference. It illustrates how d3-scale-chromatic schemes can be used to map discrete values (switch the selection menu to Discrete(n) rather than Continuous to see the code in action).
The approach would be to map each label to a slice of the color scheme:
// assumption: an array called `labels` has been created, containing the unique label values
let n = labels.length
, colorbyValue = {}
for (let i = 0; i < n; ++i) {
colorbyValue[labels[i]] = d3.rgb(d3.interpolateSpectral(i / (n - 1))).hex();
}
// then use with colorByValue[d.data.label]
You are missing a domain, try the following:
const color = d3.scaleSequential(d3.interpolateSpectral).domain([0, data.length]);
Related
I'm trying to show a vertical bar chart with x and y axes. I get the bar chart with y axis, however I'm struggling with the x-axis.
The x-axis text labels are equally distributed with the width of the bars, however: there are markers/vertical lines on the x-axis with varying width, particularly the first and last sections, even though I've specified the scaleBand and the domain.
My code:
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg class="v5chart" width="960" height="500"></svg>
<style>
/*Rectangle bar class styling*/
.bar {
fill: #0080FF
}
.bar:hover {
fill: #003366
}
/*Text class styling*/
.text {
fill: white;
font-family: sans-serif
}
</style>
<script>
////VERTICAL BAR CHART WITH SVG AND NAMES
// Create data array of values to visualize
var dataArray = [{ "Player": "John Doe", "Points": 23 }, { "Player": "Jane Doe", "Points": 13 }, { "Player": "Mary Jane", "Points": 21 }, { "Player": "Debasis Das", "Points": 14 }, { "Player": "Nishant", "Points": 37 }, { "Player": "Mark", "Points": 15 }, { "Player": "Andrew", "Points": 18 }, { "Player": "Simon", "Points": 34 }, { "Player": "Lisa", "Points": 30 }, { "Player": "Marga", "Points": 20 }];
// Create variable for the SVG
var canvas = d3.select(".v5chart1").append("g").attr("transform", "translate(20,30)");
var canvasWidth = 500;
var maxValue = d3.max(dataArray, function (d) { return d.Points; });
var canvasHeight = maxValue*10;
var heightScale = d3.scaleLinear()
.domain([0, d3.max(dataArray, function (d) { return d.Points; })])
.range([canvasHeight, 0]); //use max value (37) * 10
var y_axis = d3.axisLeft()
.scale(heightScale);
var x = d3.scaleBand()
.rangeRound([0, canvasWidth], .1);
x.domain(dataArray.map(function (d) { return d.Player; }));
var x_Axis = d3.axisBottom(x);
// Select, append to SVG, and add attributes to rectangles for bar chart
canvas.selectAll("rect")
.data(dataArray)
.enter().append("rect")
.attr("class", "bar")
.attr("height", function (d, i) { return (d.Points * 10) })
.attr("width", canvasWidth/dataArray.length)
.attr("x", function (d, i) { return (i * (canvasWidth / dataArray.length)) })
.attr("y", function (d, i) { return canvasHeight - (d.Points * 10) });
// Select, append to SVG, and add attributes to text
canvas.selectAll("text")
.data(dataArray)
.enter().append("text")
.text(function (d) { return d.Points })
.attr("class", "text")
.attr("x", function (d, i) { return (i * (canvasWidth / dataArray.length)) + (canvasWidth / dataArray.length)/2 })
.attr("y", function (d, i) { return canvasHeight + 20 - (d.Points * 10) });
canvas.append("g")
.attr("transform", "translate(0,0)")
.call(y_axis);
canvas.append("g")
.attr("transform", "translate(0," + canvasHeight + ")")
.call(x_Axis)
.selectAll("text")
.attr("x",40)
.attr("transform", function (d) {
return "rotate(65)"
});
</script>
I already checked here: https://www.d3-graph-gallery.com/graph/custom_axis.html
You should have read properly the scaleBand example on the link that you provided:
scaleBand provides a convenient bandwidth() method to provide you with the width for each bar
the idea od axis in d3js is that you don't need to do calculations yourself, so in your case you can just pass the player name to the x function and it will do the coordinate calculations for you.
same applies to the y calculations, but I leave this for you to figure out, it should not be hard at all.
one more small thing about scaleBand, you were using rangeRound() method, which I am not familiar with, but if you use range() method combined with padding() as it is in the example you linked, then by adjusting the padding value you can control the width of the bar, without affecting the x axis. The higher value, the thinner will be the bar and more space would be between the bars.
////VERTICAL BAR CHART WITH SVG AND NAMES
// Create data array of values to visualize
var dataArray = [{ "Player": "John Doe", "Points": 23 }, { "Player": "Jane Doe", "Points": 13 }, { "Player": "Mary Jane", "Points": 21 }, { "Player": "Debasis Das", "Points": 14 }, { "Player": "Nishant", "Points": 37 }, { "Player": "Mark", "Points": 15 }, { "Player": "Andrew", "Points": 18 }, { "Player": "Simon", "Points": 34 }, { "Player": "Lisa", "Points": 30 }, { "Player": "Marga", "Points": 20 }];
// Create variable for the SVG
var canvas = d3.select(".v5chart").append("g").attr("transform", "translate(20,30)");
var canvasWidth = 500;
var maxValue = d3.max(dataArray, function (d) { return d.Points; });
var heightScale = d3.scaleLinear()
.domain([0, d3.max(dataArray, function (d) { return d.Points; })])
.range([maxValue * 10, 0]); //use max value (37) * 10
var y_axis = d3.axisLeft()
.scale(heightScale);
var x = d3.scaleBand()
.range([0, canvasWidth]).padding([0.1]);
x.domain(dataArray.map(function (d) { return d.Player; }));
var x_Axis = d3.axisBottom(x);
// Select, append to SVG, and add attributes to rectangles for bar chart
canvas.selectAll("rect")
.data(dataArray)
.enter().append("rect")
.attr("class", "bar")
.attr("height", function (d, i) { return (d.Points * 10) })
.attr("width", x.bandwidth())
.attr("x", function (d, i) { return x(d.Player); })
.attr("y", function (d, i) { return 370 - (d.Points * 10) });
// Select, append to SVG, and add attributes to text
canvas.selectAll("text")
.data(dataArray)
.enter().append("text")
.text(function (d) { return d.Points })
.attr("class", "text")
.attr("text-anchor", "middle")
.attr("x", function (d, i) { return x(d.Player)+x.bandwidth()/2; })
.attr("y", function (d, i) { return 390 - (d.Points * 10) });
canvas.append("g")
.attr("transform", "translate(0,0)")
.call(y_axis);
canvas.append("g")
.attr("transform", "translate(0,370)")
.call(x_Axis);
.bar {
fill: #0080FF
}
.bar:hover {
fill: #003366
}
/*Text class styling*/
.text {
fill: white;
font-family: sans-serif
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg class="v5chart" width="960" height="500"></svg>
I have this kind of JSON
{"Arsitek":[{"tanggal":"2015-01-01","jumlah":286},{"tanggal":"2015-02-01","jumlah":601},{"tanggal":"2015-03-01","jumlah":845},{"tanggal":"2015-04-01","jumlah":550},{"tanggal":"2015-05-01","jumlah":500},{"tanggal":"2015-06-01","jumlah":201},{"tanggal":"2015-07-01","jumlah":73},{"tanggal":"2015-08-01","jumlah":503},{"tanggal":"2015-09-01","jumlah":884},{"tanggal":"2015-10-01","jumlah":782},{"tanggal":"2015-11-01","jumlah":393},{"tanggal":"2015-12-01","jumlah":150}],"DKV":[{"tanggal":"2015-01-01","jumlah":94},
I am using fetch API, and then passing the JSON to this code
function perbulan(dataset,target,terlambat){
//if(terlambat)target+='Terlambat'
document.getElementById(target).innerHTML=''
let width = 1100,height = 650
let color = d3.scaleOrdinal().range(['#334455', '#6c1f22', '#e3e8e1', '#6b112f', '#3C2F77',
'#123456', '#22c1f6', '#3e7e9e', '#b6f121', '#32C6EA',
'#A60F2B', '#648C85', '#B3F2C9', '#528C18', '#C3F25C',
'#6Af0B2', '#46C858', '#3B2F9C', '#25C881', '#3C2FC5',
'#8A23B1', '#2A81B3', '#BADCFE', '#917355', '#A1B9F5'])
let parsetime = d3.timeParse("%Y-%m-%d")
let svg = d3.select(`#${target}`)
.append('svg')
.attr('width', width)
.attr('height', height)
let xS = d3.scaleTime()
let yS = d3.scaleLinear()
let valueline = d3.line().x(d=>d.tanggal).y(d=>d.jumlah)
let axis=true;
Object.keys(dataset).forEach((data,i)=>{
let res=dataset[data].map(x=>({'jumlah':+x.jumlah,'tanggal':parsetime(x.tanggal)}))
//console.log(res)
xS.domain([d3.min(res, d=>d.tanggal),d3.max(res, d=>d.tanggal)]).range([100,1000])
yS.domain([0,d3.max(res, d=>d.jumlah)]).range([600,20])
svg.append("path")
.datum(res)
.attr("d", valueline)
.attr("class","line")
.style("stroke", color(i))//change to iterator later
if(axis){
svg.append('g')
.attr("transform", "translate(0,600)")
.call(d3.axisBottom(xS))
svg.append("g")
.attr("transform", "translate(100,-1)")
.call(d3.axisLeft(yS))
axis=false
}
})
}
But the line won't show, instead when I inspected the element it shows:
<path d="M1420045200000,286L1422723600000,601L1425142800000,845L1427821200000,550L1430413200000,500L1433091600000,201L1435683600000,73L1438362000000,503L1441040400000,884L1443632400000,782L1446310800000,393L1448902800000,150" class="line" style="stroke: rgb(51, 68, 85);"></path>
When I highlight it, the line is at coordinate 28857700000x811, so it is out of range.
I tried to manually use new Date(d.tanggal), but it does nothing.
Any ideas?
You have to use your scales. Right now, there is no scale in the line generator:
let valueline = d3.line().x(d=>d.tanggal).y(d=>d.jumlah)
Instead of that, it should be:
let valueline = d3.line().x(d => xS(d.tanggal)).y(d => yS(d.jumlah))
//scales here--------------------^---------------------^
Here is your code with that change only:
var dataset = {
"Arsitek": [{
"tanggal": "2015-01-01",
"jumlah": 286
}, {
"tanggal": "2015-02-01",
"jumlah": 601
}, {
"tanggal": "2015-03-01",
"jumlah": 845
}, {
"tanggal": "2015-04-01",
"jumlah": 550
}, {
"tanggal": "2015-05-01",
"jumlah": 500
}, {
"tanggal": "2015-06-01",
"jumlah": 201
}, {
"tanggal": "2015-07-01",
"jumlah": 73
}, {
"tanggal": "2015-08-01",
"jumlah": 503
}, {
"tanggal": "2015-09-01",
"jumlah": 884
}, {
"tanggal": "2015-10-01",
"jumlah": 782
}, {
"tanggal": "2015-11-01",
"jumlah": 393
}, {
"tanggal": "2015-12-01",
"jumlah": 150
}]
};
let width = 1100,
height = 650
let color = d3.scaleOrdinal().range(['#334455', '#6c1f22', '#e3e8e1', '#6b112f', '#3C2F77', '#123456', '#22c1f6', '#3e7e9e', '#b6f121', '#32C6EA', '#A60F2B', '#648C85', '#B3F2C9', '#528C18', '#C3F25C', '#6Af0B2', '#46C858', '#3B2F9C', '#25C881', '#3C2FC5', '#8A23B1', '#2A81B3', '#BADCFE', '#917355', '#A1B9F5'])
let parsetime = d3.timeParse("%Y-%m-%d")
let svg = d3.select("body")
.append('svg')
.attr('width', width)
.attr('height', height)
let xS = d3.scaleTime()
let yS = d3.scaleLinear()
let valueline = d3.line().x(d => xS(d.tanggal)).y(d => yS(d.jumlah))
let axis = true;
Object.keys(dataset).forEach((data, i) => {
let res = dataset[data].map(x => ({
'jumlah': +x.jumlah,
'tanggal': parsetime(x.tanggal)
}))
//console.log(res)
xS.domain([d3.min(res, d => d.tanggal), d3.max(res, d => d.tanggal)]).range([100, 1000])
yS.domain([0, d3.max(res, d => d.jumlah)]).range([600, 20]);
svg.append("path")
.datum(res)
.attr("d", valueline)
.attr("class", "line")
.style("stroke", color(i)) //change to iterator later
if (axis) {
svg.append('g')
.attr("transform", "translate(0,600)")
.call(d3.axisBottom(xS))
svg.append("g")
.attr("transform", "translate(100,-1)")
.call(d3.axisLeft(yS))
axis = false
}
})
.line {
fill: none;
stroke: teal;
stroke-width: 2px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
I am implementing a multi-line series chart using d3.js and I am getting an error pointing to my x-axis when trying to plot my dateTime from the data coming in. "Error: attribute d: Expected number, "MNaN,376.88020650…"."
Here is my function
var data = [{
"Brand": "Toyota",
"Count": 1800,
"Time": "2017-04-02 16"},
{
"Brand": "Toyota",
"Count": 1172,
"Time": "2017-04-02 17"},
{
"Brand": "Toyota",
"Count": 2000,
"Time": "2017-04-02 18"},
{
"Brand": "Honda",
"Count": 8765,
"Time": "2017-04-02 16"},
{
"Brand": "Honda",
"Count": 3445,
"Time": "2017-04-02 17"},
{
"Brand": "Honda",
"Count": 1232,
"Time": "2017-04-02 18"}
]
var dataGroup = d3.nest() //d3 method that groups data by Brand
.key(function(d) {return d.Brand;})
.entries(data);
console.log(JSON.stringify(dataGroup));
//var color = d3.scale.category10();
var vis = d3.select("#visualisation"),
WIDTH = 1000,
HEIGHT = 500,
MARGINS = {
top: 50,
right: 20,
bottom: 50,
left: 50
},
xScale = d3.scaleLinear().range([MARGINS.left, WIDTH - MARGINS.right]).domain([d3.min(data, function(d) { //set up x-axis based on data
return d.Time;
}), d3.max(data, function(d) {
return d.Time;
})]),
yScale = d3.scaleLinear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([d3.min(data, function(d) { //set up y-axis based on data
return d.Count;
}), d3.max(data, function(d) {
return d.Count;
})]),
xAxis = d3.axisBottom()
.scale(xScale),
yAxis = d3.axisLeft()
.scale(yScale)
vis.append("svg:g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")")
.call(xAxis);
vis.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(" + (MARGINS.left) + ",0)")
.call(yAxis);
var lineGen = d3.line()
.x(function(d) {
return xScale(d.Time);
})
.y(function(d) {
return yScale(d.Count);
})
.curve(d3.curveBasis);
dataGroup.forEach(function(d,i) { //iterate over the dataGroup and create line graph for each brand
vis.append('svg:path')
.attr('d', lineGen(d.values))
.attr('stroke', function(d,j) {
return "hsl(" + Math.random() * 360 + ",100%,50%)"; //random color for each brand line on graph
})
.attr('stroke-width', 2)
.attr('id', 'line_'+d.key)
.attr('fill', 'none');
lSpace = WIDTH/dataGroup.length; //define the legend space based on number of brands
vis.append("text")
.attr("x", (lSpace/2)+i*lSpace)
.attr("y", HEIGHT)
.style("fill", "black")
.attr("class","legend")
.on('click',function(){
var active = d.active ? false : true;
var opacity = active ? 0 : 1;
d3.select("#line_" + d.key).style("opacity", opacity);
d.active = active;
})
.text(d.key);
});
My dates are in yyyy-mm-dd HH format and what I am trying to accomplish is this for example:
"Time": "2017-04-02 16" converted to 'April 02' on the x axis and have the hour (HH) just displayed as a tool tip...etc
Here is a jsfiddle link https://jsfiddle.net/rsov2s2s/
Any help is appreciated.
In your data objects, Time is only a string. Thus, you`ll have to parse it into an actual date:
data.forEach(function(d){
d.Time = d3.timeParse("%Y-%m-%d %H")(d.Time)
});
In this function, d3.timeParse uses "%Y-%m-%d %H" as a specifier, which matches the structure of your strings.
After that, don't forget to change the xScale from scaleLinear to scaleTime.
Here is your code with those changes only:
var data = [{
"Brand": "Toyota",
"Count": 1800,
"Time": "2017-04-02 16"
}, {
"Brand": "Toyota",
"Count": 1172,
"Time": "2017-04-02 17"
}, {
"Brand": "Toyota",
"Count": 2000,
"Time": "2017-04-02 18"
}, {
"Brand": "Honda",
"Count": 8765,
"Time": "2017-04-02 16"
}, {
"Brand": "Honda",
"Count": 3445,
"Time": "2017-04-02 17"
}, {
"Brand": "Honda",
"Count": 1232,
"Time": "2017-04-02 18"
}];
data.forEach(function(d) {
d.Time = d3.timeParse("%Y-%m-%d %H")(d.Time)
});
var dataGroup = d3.nest() //d3 method that groups data by Brand
.key(function(d) {
return d.Brand;
})
.entries(data);
//var color = d3.scale.category10();
var vis = d3.select("#visualisation"),
WIDTH = 1000,
HEIGHT = 500,
MARGINS = {
top: 50,
right: 20,
bottom: 50,
left: 50
},
xScale = d3.scaleTime().range([MARGINS.left, WIDTH - MARGINS.right]).domain([d3.min(data, function(d) { //set up x-axis based on data
return d.Time;
}), d3.max(data, function(d) {
return d.Time;
})]),
yScale = d3.scaleLinear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([d3.min(data, function(d) { //set up y-axis based on data
return d.Count;
}), d3.max(data, function(d) {
return d.Count;
})]),
xAxis = d3.axisBottom()
.scale(xScale),
yAxis = d3.axisLeft()
.scale(yScale)
vis.append("svg:g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")")
.call(xAxis);
vis.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(" + (MARGINS.left) + ",0)")
.call(yAxis);
var lineGen = d3.line()
.x(function(d) {
return xScale(d.Time);
})
.y(function(d) {
return yScale(d.Count);
})
.curve(d3.curveBasis);
dataGroup.forEach(function(d, i) { //iterate over the dataGroup and create line graph for each brand
vis.append('svg:path')
.attr('d', lineGen(d.values))
.attr('stroke', function(d, j) {
return "hsl(" + Math.random() * 360 + ",100%,50%)"; //random color for each brand line on graph
})
.attr('stroke-width', 2)
.attr('id', 'line_' + d.key)
.attr('fill', 'none');
lSpace = WIDTH / dataGroup.length; //define the legend space based on number of brands
vis.append("text")
.attr("x", (lSpace / 2) + i * lSpace)
.attr("y", HEIGHT)
.style("fill", "black")
.attr("class", "legend")
.on('click', function() {
var active = d.active ? false : true;
var opacity = active ? 0 : 1;
d3.select("#line_" + d.key).style("opacity", opacity);
d.active = active;
})
.text(d.key);
});
.axis path {
fill: none;
stroke: #777;
shape-rendering: crispEdges;
}
.axis text {
font-family: Lato;
font-size: 13px;
}
.legend {
font-size: 14px;
font-weight: bold;
cursor: pointer;
<title>D3 Test</title>
<script src="https://d3js.org/d3.v4.js"></script>
<body>
<svg id="visualisation" width="1000" height="600"></svg>
<script src="InitChart.js"></script>
</body>
There is example how to have a label on the node in a D3 forced graph. What I try to do is to have a label on the line instead.
Example of the label on node: http://bl.ocks.org/mbostock/2706022
This code will display the text for the line up in the left corner. It seems that it takes the x, y cordinates from the canvas and not from my line. How to fix this?
var labelLine = olinks.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text("eeeeee");
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
</style>
<body>
<script src="./Script/d3.v3/d3.v3.min.js"></script>
<script>
var graph = {
"nodes": [
{ "name": "App1-main", "group": 1 },
{ "name": "App2", "group": 1 },
{ "name": "App3", "group": 1 },
{ "name": "App4", "group": 1 },
{ "name": "Content-1", "group": 3 },
{ "name": "Content-1", "group": 3 },
{ "name": "Content-1", "group": 3 },
{ "name": "Content-1", "group": 3 },
{ "name": "Pontmercy", "group": 3 }
],
"links": [
{ "source": 1, "target": 0, "value": 1 },
{ "source": 2, "target": 0, "value": 1 },
{ "source": 0, "target": 3, "value": 1 }
]
};
var width = 960,
height = 500;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-300)
.linkDistance(60)
.size([width, height]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var drawGraph = function (graph) {
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var olinks = svg.selectAll("g.link")
.data(graph.links)
.enter().append("g")
.call(force.drag);
var link = olinks.append("line")
.attr("class", "link")
.style("stroke-width", function (d) { return Math.sqrt(d.value); });
var labelLine = olinks.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text("eeeeee");
var gnodes = svg.selectAll('g.gnode')
.data(graph.nodes)
.enter()
.append('g')
.classed('gnode', true);
var node = gnodes.append("circle")
.attr("class", "node")
.attr("r", 5)
.style("fill", function (d) { return color(d.group); })
.call(force.drag);
var labels = gnodes.append("text")
.attr("x", 12)
.attr("dy", ".35em")
.text(function (d) { return d.name; });
console.log(labels);
force.on("tick", function () {
link.attr("x1", function (d) { return d.source.x; })
.attr("y1", function (d) { return d.source.y; })
.attr("x2", function (d) { return d.target.x; })
.attr("y2", function (d) { return d.target.y; });
gnodes.attr("transform", function (d) {
return 'translate(' + [d.x, d.y] + ')';
});
});
};
drawGraph(graph);
</script>
This is an example that has correct behavior.
The key points are here:
1) You need to define link as SVG "g" element, so that you can define both lines and labels for each link, and that coordinates are computed correctly.
2) Label text must be centered horizontally (code: .attr("text-anchor", "middle")).
3) Inside tick(), you need to compute coordinate of the labels., as arithmetic mean between source and target node.
Hope this helps.
There also was another similar question recently.
Here is a link to the jsfiddle
http://jsfiddle.net/jaimem/RPGPL/2/
Now the graph shows red color for all the circles.Is dere a way to show random colors on the circles.
Here is the d3.js code
var data = [{ "count": "202", "year": "1590"},
{ "count": "215", "year": "1592"},
{ "count": "179", "year": "1593"},
{ "count": "199", "year": "1594"},
{ "count": "134", "year": "1595"},
{ "count": "176", "year": "1596"},
{ "count": "172", "year": "1597"},
{ "count": "161", "year": "1598"},
{ "count": "199", "year": "1599"},
{ "count": "181", "year": "1600"},
{ "count": "157", "year": "1602"},
{ "count": "179", "year": "1603"},
{ "count": "150", "year": "1606"},
{ "count": "187", "year": "1607"},
{ "count": "133", "year": "1608"},
{ "count": "190", "year": "1609"},
{ "count": "175", "year": "1610"},
{ "count": "91", "year": "1611"},
{ "count": "150", "year": "1612"} ];
function ShowGraph(data) {
d3.selectAll('.axis').remove();
var vis = d3.select("#visualisation").append('svg'),
WIDTH = 500,
HEIGHT = 500,
MARGINS = {
top: 20,
right: 20,
bottom: 20,
left: 30
},
xRange = d3.scale
.linear()
.domain([
d3.min(data, function(d){ return parseInt(d.year, 10);}),
d3.max(data, function(d){ return parseInt(d.year, 10);})
])
.range([MARGINS.left, WIDTH - MARGINS.right]),
yRange = d3.scale
.linear()
.domain([
d3.min(data, function(d){ return parseInt(d.count, 10);}),
d3.max(data, function(d){ return parseInt(d.count, 10);})
])
.range([HEIGHT - MARGINS.top, MARGINS.bottom]),
xAxis = d3.svg.axis() // generate an axis
.scale(xRange) // set the range of the axis
.tickSize(5) // height of the ticks
.tickSubdivide(true), // display ticks between text labels
yAxis = d3.svg.axis() // generate an axis
.scale(yRange) // set the range of the axis
.tickSize(5) // width of the ticks
.orient("left") // have the text labels on the left hand side
.tickSubdivide(true); // display ticks between text labels
var transition = vis.transition().duration(1000).ease("exp-in-out");
transition.select(".x.axis").call(xAxis);
transition.select(".y.axis").call(yAxis);
vis.append("svg:g") // add a container for the axis
.attr("class", "x axis") // add some classes so we can style it
.attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")") // move it into position
.call(xAxis); // finally, add the axis to the visualisation
vis.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(" + (MARGINS.left) + ",0)")
.call(yAxis);
var circles = vis.selectAll("circle").data(data)
circles.enter()
.append("svg:circle")
.attr("cx", function (d) {
return xRange(d.year);
})
.attr("cy", function (d) {
return yRange(d.count);
})
.style("fill", "red")
circles.transition().duration(1000)
.attr("cx", function (d) {
return xRange(d.year);
})
.attr("cy", function (d) {
return yRange(d.count);
})
.attr("r", 10)
circles.exit()
.transition().duration(1000)
.attr("r", 10)
.remove();
}
you can also use d3.scale.category20(); to get some predefined random colors
Just define color scale as
var color = d3.scale.category20();
Add add fill attribute to the circles as
.attr("fill",function(d,i){return color(i);});
replace .style("fill","red") with
.style("fill",function() {
return "hsl(" + Math.random() * 360 + ",100%,50%)";
})
doc for dynamic properties
For a quick-and-dirty approach to random colors:
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
d3.select("body").selectAll("div")
.data(dataset)
.enter()
.append("div")
.attr("class", "bar")
.style('height',(data) => { return data+'px' })
.style('background-color',() => {
let color = '#'+Math.floor(Math.random() * Math.pow(2,32) ^ 0xffffff).toString(16).substr(-6);
console.log(color);
return color;
})
.bar {
width: 25px;
height: 100px;
display: inline-block;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.js"></script>
May be the Chumliu answer is the first approach, but it has one fault: it will repeat colors and make a confusion for the when read the graphics.
Like this way you have different colors:
var colors = [];
var arr = [];
var j;
products.forEach(function(d)
{
do
{
j = Math.random();
}
while($.inArray(j,arr) != -1);
arr.push(j);
//this gives us different values
var value = parseFloat(d.category_id) + parseFloat(d.total);
eval('colors.cat'+d.category_id+' = "hsl('+ parseFloat('0.'+ value ) * 360 + ',100%,50%)"');
}
later you can use it in D3 like this:
g.append("path").style("fill", function(d)
{
var indexcolor = 'cat'+d.data.category_id; return colors[indexcolor];
});