I've implementing barchart to denote my result set. I am trying to create this design using c3js. I referred the documentation but have not getting the shaded design. Please someone guide me to solve this.
My Code Snippet is,
var chart = c3.generate({
bindto: '#chart',
size : {
width: 600,
height: 200
},
data: {
columns: [
["data1", 40, 20, 20, 20],
["data2", 20, 10, 30, 30]
],
type: 'bar',
groups: [
['data1', 'data2']
],
order: null,
labels: {
format: function(v, id, i, j) {
return v;
}
}
},
bar: {
space: 0.2,
width: {
ratio: 0.2 // this makes bar width 50% of length between ticks
}
},
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.3.0/c3.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.3.0/c3.min.js"></script>
<div id="chart"></div>
My requirement is to design like below image in bars in barchart,
Thanks
The solution in the snippet introduces some d3 SVG manipulation which puts a region over the bars of each targeted series. The region has a hatched grey line and a semi-transparent fill. The function to do this is written to be portable i.e. outside of the C3 construtor params, and is invoked by the onrendered c3 event.
The doClone() function duplicates nodes in place which is useful to know, and the creation of the pattern and use as a colour via 'url(#patternid)' is also a potentially useful technique.
Also available at https://codepen.io/JEE42/pen/yRWbvq
function doClone(node){ // clone the given node
return d3.select(node.parentNode.insertBefore(node.cloneNode(true), node.nextSibling));
}
function hatchBars(hatchTargets){ // Place a hatching pattern over the target bars.
for (var i = 0; i < hatchTargets.length; i = i + 1){
d3.select('.c3-bars-' + hatchTargets[i]).each(function(d, i){
d3.select(this).selectAll('path').each(function(d, i){
var node = d3.select(this).node();
var daClone = doClone(node);
daClone
.style('fill', 'url(#hash4_4)')
.style('stroke', 'url(#hash4_4)');
});
})
}
}
c3.chart.internal.fn.afterInit = function () {
d3.select('defs')
.append('pattern')
.attr('id', "hash4_4") // use id to get handle in a moment
.attr('width', 14)
.attr('height', 14)
.attr('patternUnits', "userSpaceOnUse")
.attr('patternTransform', "rotate(45 0 0 )")
.append("rect")
.attr('width', 14)
.attr('height', 14)
.attr('fill', '#00000000') // transparent background
d3.select('#hash4_4') // get the pattenn
.append('line') // add a line
.attr('y2', 14)
.style('stroke', "#00000044") // semi-transparent bars
.attr('stroke-width', 14)
};
//
// Standard C3 chart render with one twist which is the onrendered event call at the end.
//
var chart = c3.generate({
bindto: '#chart',
data: {
columns: [
['data1', -30, 200, 200, 400, -150, 250],
['data2', 130, 100, -100, 200, -150, 50],
],
type: 'bar',
groups: [
['data1', 'data2']
]
},
grid: {
y: {
lines: [{value:0}]
}
},
onrendered: function () { // execute after drawn
hatchBars(['data2']); // Place a hatching pattern over the target bars.
}
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.7/c3.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.7/c3.min.js"></script>
<div class='chart-wrapper'>
<div class='chat' id="chart"></div>
</div>
Related
TLDR: I have an NVD3 graph that shows tick lines all across the axis, but I would like to change it so it only displays on the axis lines if possible.
Here is a live example:
var app = angular.module('plunker', ['nvd3']);
app.controller('MainCtrl', function($scope) {
$scope.options = {
chart: {
type: 'lineChart',
height: 450,
margin : {
top: 20,
right: 20,
bottom: 80,
left: 55
},
x: function(d){ return d.x; },
y: function(d){ return d.y; },
useInteractiveGuideline: true,
xAxis: {
axisLabel: 'Timeline',
tickFormat: function(d) {
return d3.time.format('%B %d')(new Date(d))
},
ticks: 6,
showMaxMin: false
},
yAxis: {
axisLabel: 'Molecular density (kg/m^3)',
tickFormat: function(d){
return d3.format('.02f')(d);
},
axisLabelDistance: -10,
showMaxMin: false
}
}
};
$scope.data = [{"key":"K7 molecules","values":[{"x":1435708800000,"y":8},{"x":1435795200000,"y":9},{"x":1435881600000,"y":8},{"x":1435968000000,"y":8},{"x":1436054400000,"y":9},{"x":1436140800000,"y":9},{"x":1436227200000,"y":8},{"x":1436313600000,"y":8},{"x":1436400000000,"y":9},{"x":1436486400000,"y":9},{"x":1436572800000,"y":7},{"x":1436659200000,"y":8}],"area":true,"color":"#0CB3EE"},{"key":"N41 type C molecules","values":[{"x":1435708800000,"y":8},{"x":1435795200000,"y":7},{"x":1435881600000,"y":8},{"x":1435968000000,"y":9},{"x":1436054400000,"y":7},{"x":1436140800000,"y":9},{"x":1436227200000,"y":8},{"x":1436313600000,"y":9},{"x":1436400000000,"y":9},{"x":1436486400000,"y":9},{"x":1436572800000,"y":9},{"x":1436659200000,"y":8}],"area":true,"color":"#383838"}];
});
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>Angular-nvD3 Line Chart</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.1/nv.d3.min.css"/>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.9/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.1/nv.d3.min.js"></script>
<script src="https://rawgit.com/krispo/angular-nvd3/v1.0.1/dist/angular-nvd3.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<nvd3 options="options" data="data" class="with-3d-shadow with-transitions"></nvd3>
</body>
</html>
Is there any way I could make the tick lines appear just on the axes line only? To make it clear, this is what it looks like:
I used a different library to generate the following plot, and I would like the tick lines to appear just on the axis lines like this example instead:
It appears that there is no real way to do this with NVD3 as it does not provide a way to show tick marks on the axis. However, we could add our own tick marks by fetching the chart SVG and then modifying it.
I've attached an example that adds tick marks to X-Axis, and it is basically slightly modified based on this jsfiddle here: http://jsfiddle.net/3r88bgjw
var data;
data = [{
values: [],
}, ];
var i, x;
var prevVal = 3000;
var tickCount = 2000;
for (i = 0; i < tickCount; i++) {
x = 1425096000 + i * 10 * 60; // data points every ten minutes
if (Math.random() < 0.8) { // add some gaps
prevVal += (Math.random() - 0.5) * 500;
if (prevVal <= 0) {
prevVal = Math.random() * 100;
}
data[0].values.push({
x: x * 1000,
y: prevVal
});
}
}
var chart;
nv.addGraph(function() {
chart = nv.models.historicalBarChart();
chart.xScale(d3.time.scale()) // use a time scale instead of plain numbers in order to get nice round default values in the axis
.color(['#68c'])
.useInteractiveGuideline(true) // check out the css that turns the guideline into this nice thing
.margin({
"left": 80,
"right": 50,
"top": 20,
"bottom": 30,
})
.noData("There is no data to display.")
.duration(0);
var tickMultiFormat = d3.time.format.multi([
["%-I:%M%p", function(d) {
return d.getMinutes();
}], // not the beginning of the hour
["%-I%p", function(d) {
return d.getHours();
}], // not midnight
["%b %-d", function(d) {
return d.getDate() != 1;
}], // not the first of the month
["%b %-d", function(d) {
return d.getMonth();
}], // not Jan 1st
["%Y", function() {
return true;
}]
]);
chart.xAxis
.showMaxMin(false)
.tickPadding(10)
.tickFormat(function(d) {
return tickMultiFormat(new Date(d));
});
chart.yAxis
.tickFormat(d3.format(",.0f"));
var svgElem = d3.select('#chart svg');
svgElem
.datum(data)
.transition()
.call(chart);
// make our own x-axis tick marks because NVD3 doesn't provide any
var tickY2 = chart.yAxis.scale().range()[1];
var lineElems = svgElem
.select('.nv-x.nv-axis.nvd3-svg')
.select('.nvd3.nv-wrap.nv-axis')
.select('g')
.selectAll('.tick')
.data(chart.xScale().ticks())
.append('line')
.attr('class', 'x-axis-tick-mark')
.attr('x2', 0)
.attr('y1', tickY2 + 7)
.attr('y2', tickY2)
.attr('stroke-width', 3);
// set up the tooltip to display full dates
var tsFormat = d3.time.format('%b %-d, %Y %I:%M%p');
var contentGenerator = chart.interactiveLayer.tooltip.contentGenerator();
var tooltip = chart.interactiveLayer.tooltip;
tooltip.contentGenerator(function(d) {
d.value = d.series[0].data.x;
return contentGenerator(d);
});
tooltip.headerFormatter(function(d) {
return tsFormat(new Date(d));
});
return chart;
});
<div>Try resizing the panel to see the various types of time labels.</div>
<br>
<div id="chart">
<svg></svg>
</div>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.4/nv.d3.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.4/nv.d3.min.js"></script>
I started off with this example: https://www.amcharts.com/demos/floating-bar-chart/ but want the floating-aspect also cover a date axis. This way I will be able to color a rectangular area in my charts.
I made the following changes:
Changed the x-axis to a date axis
Changed the y-axis to a value axis
Changed the data correspondingly
Changed the data-fields in the series-definition to use openValueXField, valueXField for the dates and openValueYField and valueYField for the values
Something (vertical colored lines) are displayed in the most left part of the chart but no colored areas.
When using values for both axes it works perfectly but not with dates. I hope some of you knows what is wrong here. The Amcharts Demo's only provide example with one axis.
The two source codes are included hereafter.
//
// Source Code for two numeric axis -> working
//
<meta content="text/html;charset=utf-6" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<!-- Styles -->
<style>
#chartdiv {
width: 100%;
height: 500px;
}
</style>
<!-- Resources -->
<script src="https://cdn.amcharts.com/lib/5/index.js"></script>
<script src="https://cdn.amcharts.com/lib/5/xy.js"></script>
<script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
<!-- Chart code -->
<script>
am5.ready(function() {
// Create root element
// https://www.amcharts.com/docs/v5/getting-started/#Root_element
var root = am5.Root.new("chartdiv");
// Set themes
// https://www.amcharts.com/docs/v5/concepts/themes/
root.setThemes([
am5themes_Animated.new(root)
]);
// Create chart
// https://www.amcharts.com/docs/v5/charts/xy-chart/
var chart = root.container.children.push(am5xy.XYChart.new(root, {
panX: false,
panY: false,
wheelX: "panX",
wheelY: "zoomX",
layout: root.verticalLayout
}));
// Add legend
// https://www.amcharts.com/docs/v5/charts/xy-chart/legend-xy-series/
var legend = chart.children.push(am5.Legend.new(root, {
centerX: am5.p50,
x: am5.p50
}))
var colors = chart.get("colors");
// Data
var data = [{
name: "John",
startTime: 8,
endTime: 11,
startValue: 10,
endValue: 14,
columnSettings: {
stroke: colors.getIndex(1),
fill: colors.getIndex(1)
}
}, {
name: "Joe",
startTime: 10,
endTime: 13,
startValue: 12,
endValue: 17,
columnSettings: {
stroke: colors.getIndex(3),
fill: colors.getIndex(3)
}
}];
// Create axes
// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/
var yAxis = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {pan:"zoom"}),
})
);
var xAxis = chart.xAxes.push(
am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererX.new(root, {pan:"zoom"}),
})
);
// Add series
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
var series = chart.series.push(am5xy.ColumnSeries.new(root, {
name: "Income",
xAxis: xAxis,
yAxis: yAxis,
openValueXField: "startTime",
valueXField: "endTime",
openValueYField: "startValue",
valueYField: "endValue",
sequencedInterpolation: true
}));
series.columns.template.setAll({
height: am5.percent(100),
templateField: "columnSettings",
tooltipText: "[bold]{name}[/]\n{categoryY}: {valueX}"
});
series.data.setAll(data);
// Make stuff animate on load
// https://www.amcharts.com/docs/v5/concepts/animations/
series.appear();
chart.appear(1000, 100);
}); // end am5.ready()
</script>
<!-- HTML -->
<div id="chartdiv"></div>
91,31 43%
//
// Source Code for one date and one numeric axis -> NOT working
//
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<!-- Styles -->
<style>
#chartdiv {
width: 100%;
height: 500px;
}
</style>
<!-- Resources -->
<script src="https://cdn.amcharts.com/lib/5/index.js"></script>
<script src="https://cdn.amcharts.com/lib/5/xy.js"></script>
<script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
<!-- Chart code -->
<script>
am5.ready(function() {
// Create root element
// https://www.amcharts.com/docs/v5/getting-started/#Root_element
var root = am5.Root.new("chartdiv");
// Set themes
// https://www.amcharts.com/docs/v5/concepts/themes/
root.setThemes([
am5themes_Animated.new(root)
]);
// Create chart
// https://www.amcharts.com/docs/v5/charts/xy-chart/
var chart = root.container.children.push(am5xy.XYChart.new(root, {
panX: false,
panY: false,
wheelX: "panX",
wheelY: "zoomX",
layout: root.verticalLayout
}));
// Add legend
// https://www.amcharts.com/docs/v5/charts/xy-chart/legend-xy-series/
var legend = chart.children.push(am5.Legend.new(root, {
centerX: am5.p50,
x: am5.p50
}))
var colors = chart.get("colors");
// Data
var data = [{
name: "John",
startTime: new Date(2021, 1, 28),
endTime: new Date(2021, 3, 17),
startValue: 10,
endValue: 14,
columnSettings: {
stroke: colors.getIndex(1),
fill: colors.getIndex(1)
}
}, {
name: "Joe",
startTime: new Date(2021, 2, 5),
endTime: new Date(2021, 5, 7),
startValue: 12,
endValue: 17,
columnSettings: {
stroke: colors.getIndex(3),
fill: colors.getIndex(3)
}
}];
// Create axes
// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/
var yAxis = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {pan:"zoom"}),
})
);
var xAxis = chart.xAxes.push(
am5xy.DateAxis.new(root, {
renderer: am5xy.AxisRendererX.new(root, {
pan:"zoom",
minimumDate: new Date(2021, 1, 18),
maximumDate: new Date(2021, 7, 20),
}),
})
);
// Add series
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
var series = chart.series.push(am5xy.ColumnSeries.new(root, {
name: "Income",
xAxis: xAxis,
yAxis: yAxis,
openValueXField: "startTime",
valueXField: "endTime",
openValueYField: "startValue",
valueYField: "endValue",
sequencedInterpolation: true
}));
series.columns.template.setAll({
height: am5.percent(100),
templateField: "columnSettings",
tooltipText: "[bold]{name}[/]\n{categoryY}: {valueX}"
});
series.data.setAll(data);
// Make stuff animate on load
// https://www.amcharts.com/docs/v5/concepts/animations/
series.appear();
chart.appear(1000, 100);
}); // end am5.ready()
</script>
<!-- HTML -->
<div id="chartdiv"></div>
94,3 97%
The first time I load data, the graph draws correctly, but when I load a different data set, the graph remains unchanged.
I switch between datasets using buttons. The first click always draws the graph correctly, no matter what button I click. But I can't update the graph after it is drawn by clicking on the other button. Any help is very much appreciated,thank you!
const dataA = [
{ population: 50, size: 100 },
{ population: 100, size: 100 },
];
const dataB = [
{ money: 4, currency: "usd" },
{ money: 10, currency: "eur" },
];
function drawChart(dataSet, prop) {
let width = 900;
let height = 200;
let x = d3.scale.ordinal().rangeRoundBands([0, width], 0.9);
let y = d3.scale
.linear()
.domain([dataSet[0][prop] - 39, dataSet[dataSet.length - 1][prop]])
.range([height, 0]);
let chart = d3.select("#chart").attr("width", width).attr("height", height);
let barWidth = width / dataSet.length;
let div = d3.select("body").append("div").attr("class", "tooltip");
let bar = chart
.selectAll("g")
.data(dataSet)
.enter()
.append("g")
.attr("transform", function (d, i) {
return "translate(" + i * barWidth + ",0)";
});
bar
.append("rect")
.attr("y", function (d) {
return y(d[prop]);
})
.attr("height", function (d) {
return height - y(d[prop]);
})
.attr("width", barWidth);
}
function drawDataA() {
drawChart(dataA, "population");
}
function drawDataB() {
drawChart(dataB, "money");
}
d3.select("#dataA").on("click", drawDataA);
d3.select("#dataB").on("click", drawDataB);
<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head>
<script src="https://d3js.org/d3.v4.js"></script>
</head>
<body>
<div id="app">
<svg class="chart" id="chart"></svg>
<button id="dataA">data1</button>
<button id="dataB">data2</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="./index.js"></script>
</body>
</html>
CodePen: https://codepen.io/rfripp2/pen/porpaLL
This is the expected behavior. Let's look at your code:
let bar = chart
.selectAll("g")
.data(dataSet)
.enter()
.append("g")
The select all statement selects all existing elements matching the selector that are children of elements in the selection chart.
The data method binds a new data array to this selection.
The enter method returns a new selection, containing a placeholder for every item in the data array which does not have a corresponding element in the selection.
The append method returns a newly appended child element for every element in the selection it is called on.
Running the code
The first time you call the draw function you have no g elements, so the selection is empty. You bind data to this empty selection. You then use the enter selection. Because there are two data items and no elements in the selection, enter contains two placeholders/elements. You then use append to add those elements.
The second time you call the draw function you have two g elements, so the selection has two elements in it. You bind data to this selection. You then use the enter selection. Because you already have two elements and you only have two data points, the enter selection is empty. As a consequence, append does not create any new elements.
You can see this by using selection.size():
const dataA = [
{ population: 50, size: 100 },
{ population: 100, size: 100 },
];
const dataB = [
{ money: 4, currency: "usd" },
{ money: 10, currency: "eur" },
];
function drawChart(dataSet, prop) {
let width = 900;
let height = 200;
let x = d3.scale.ordinal().rangeRoundBands([0, width], 0.9);
let y = d3.scale
.linear()
.domain([dataSet[0][prop] - 39, dataSet[dataSet.length - 1][prop]])
.range([height, 0]);
let chart = d3.select("#chart").attr("width", width).attr("height", height);
let barWidth = width / dataSet.length;
let div = d3.select("body").append("div").attr("class", "tooltip");
let bar = chart
.selectAll("g")
.data(dataSet)
.enter()
.append("g")
.attr("transform", function (d, i) {
return "translate(" + i * barWidth + ",0)";
});
console.log("The enter selection contains: " + bar.size() + "elements")
bar
.append("rect")
.attr("y", function (d) {
return y(d[prop]);
})
.attr("height", function (d) {
return height - y(d[prop]);
})
.attr("width", barWidth);
}
function drawDataA() {
drawChart(dataA, "population");
}
function drawDataB() {
drawChart(dataB, "money");
}
d3.select("#dataA").on("click", drawDataA);
d3.select("#dataB").on("click", drawDataB);
<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head>
<script src="https://d3js.org/d3.v4.js"></script>
</head>
<body>
<div id="app">
<svg class="chart" id="chart"></svg>
<button id="dataA">data1</button>
<button id="dataB">data2</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="./index.js"></script>
</body>
</html>
Solution
We want to use both the update and the enter selection (if the dataset ever changes in size, we'd likely want the exit selection too). We can use .join() to simplify this, join removes elements in the exit selection (surplus elements which don't have a corresponding data item), and returns the merged enter selection (new elements for surplus data items) and update selection (preexisting elements).
The nesting of your elements into a parent g and child rect is unnecessary here - and requires additional modifications. By positioning the bars directly we avoid the need for the parent g:
const dataA = [
{ population: 50, size: 100 },
{ population: 100, size: 100 },
];
const dataB = [
{ money: 4, currency: "usd" },
{ money: 10, currency: "eur" },
];
function drawChart(dataSet, prop) {
let width = 400;
let height = 200;
let x = d3.scaleBand().range([0, width], 0.9);
let y = d3.scaleLinear()
.domain([dataSet[0][prop] - 39, dataSet[dataSet.length - 1][prop]])
.range([height, 0]);
let chart = d3.select("#chart").attr("width", width).attr("height", height);
let barWidth = width / dataSet.length;
let div = d3.select("body").append("div").attr("class", "tooltip");
let bar = chart
.selectAll("rect")
.data(dataSet)
.join("rect")
.attr("transform", function (d, i) {
return "translate(" + i * barWidth + ",0)";
}).attr("y", function (d) {
return y(d[prop]);
})
.attr("height", function (d) {
return height - y(d[prop]);
})
.attr("width", barWidth);
}
function drawDataA() {
drawChart(dataA, "population");
}
function drawDataB() {
drawChart(dataB, "money");
}
d3.select("#dataA").on("click", drawDataA);
d3.select("#dataB").on("click", drawDataB);
<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head>
</head>
<body>
<div id="app">
<svg class="chart" id="chart"></svg>
<br />
<button id="dataA">data1</button>
<button id="dataB">data2</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
<script src="./index.js"></script>
</body>
</html>
This requires updating your version of D3 from v3 (you are actually using two versions of D3, v3 and v4, both fairly outdated, and both with different method names, actually with different ways of handling the enter selection).
If you wish to use d3v4, then the join method is not available, but we can merge enter and update:
When I say update selection, I'm refering to the initial selection:
// update selection:
let bar = chart
.selectAll("rect")
.data(dataSet);
// enter selection:
bar.enter().append("rect")
const dataA = [
{ population: 50, size: 100 },
{ population: 100, size: 100 },
];
const dataB = [
{ money: 4, currency: "usd" },
{ money: 10, currency: "eur" },
];
function drawChart(dataSet, prop) {
let width = 400;
let height = 200;
let x = d3.scaleBand().range([0, width], 0.9);
let y = d3.scaleLinear()
.domain([dataSet[0][prop] - 39, dataSet[dataSet.length - 1][prop]])
.range([height, 0]);
let chart = d3.select("#chart").attr("width", width).attr("height", height);
let barWidth = width / dataSet.length;
let div = d3.select("body").append("div").attr("class", "tooltip");
let bar = chart
.selectAll("rect")
.data(dataSet);
bar.enter().append("rect")
.merge(bar)
.attr("transform", function (d, i) {
return "translate(" + i * barWidth + ",0)";
}).attr("y", function (d) {
return y(d[prop]);
})
.attr("height", function (d) {
return height - y(d[prop]);
})
.attr("width", barWidth);
}
function drawDataA() {
drawChart(dataA, "population");
}
function drawDataB() {
drawChart(dataB, "money");
}
d3.select("#dataA").on("click", drawDataA);
d3.select("#dataB").on("click", drawDataB);
<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head>
</head>
<body>
<div id="app">
<svg class="chart" id="chart"></svg>
<br />
<button id="dataA">data1</button>
<button id="dataB">data2</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.1.0/d3.min.js"></script>
<script src="./index.js"></script>
</body>
</html>
And lastly, if you wish to keep d3v3 (we are on v7 already), we can rely on an implicit merging of update and enter on enter (modifying the update selection). This "magic" was removed in v4, partly because it was not explicit. To do so we need to break your method chaining so that bar contains the
const dataA = [
{ population: 50, size: 100 },
{ population: 100, size: 100 },
];
const dataB = [
{ money: 4, currency: "usd" },
{ money: 10, currency: "eur" },
];
function drawChart(dataSet, prop) {
let width = 400;
let height = 200;
let x = d3.scale.ordinal().rangeRoundBands([0, width], 0.9);
let y = d3.scale.linear()
.domain([dataSet[0][prop] - 39, dataSet[dataSet.length - 1][prop]])
.range([height, 0]);
let chart = d3.select("#chart").attr("width", width).attr("height", height);
let barWidth = width / dataSet.length;
let div = d3.select("body").append("div").attr("class", "tooltip");
// update selection:
let bar = chart
.selectAll("rect")
.data(dataSet);
// enter selection:
bar.enter().append("rect")
bar.attr("transform", function (d, i) {
return "translate(" + i * barWidth + ",0)";
}).attr("y", function (d) {
return y(d[prop]);
})
.attr("height", function (d) {
return height - y(d[prop]);
})
.attr("width", barWidth);
}
function drawDataA() {
drawChart(dataA, "population");
}
function drawDataB() {
drawChart(dataB, "money");
}
d3.select("#dataA").on("click", drawDataA);
d3.select("#dataB").on("click", drawDataB);
<!DOCTYPE html>
<html>
<meta charset="utf-8" />
<head>
</head>
<body>
<div id="app">
<svg class="chart" id="chart"></svg>
<br />
<button id="dataA">data1</button>
<button id="dataB">data2</button>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="./index.js"></script>
</body>
</html>
Note: d3v4 made changes to method names that break code from v3. This required changes to d3.scale.linear / d3.scale.ordinal in the snippets using v4 and 7 (using merge and join respectively).
I'm trying to place an svg element on a c3 chart such that it aligns horizontally with one of the tick marks.
See fiddle here: https://jsfiddle.net/4sw82k1z/3/
For example, I'd like the red label to be centered above tick mark '1' and the green label centered above tick mark '3'.
I'm new to the d3 world. The way that I've tried to accomplish this is in absolute coordinates, which fail when the 'html' pane of the fiddle is resized. To see what I mean, click drag the size of the 'html' window.
var rightlabel = d3.select("#chart svg")
.append("svg").attr("width", 1200).attr("height", 50) //container for txt
.append("text").text("i'm a label in the green region") //text element
.style("fill", "green").attr("transform","translate(400,20)");
What's the proper way to achieve this?
Hook the redrawed event and reposition the labels. You can also set position properly based on some of the internals of c3:
var chart = c3.generate({
data: {
columns: [
['data1', 100, 250, 100, 300, 75],
['data2', 60, 500, 250, 450, 300],
]
},
onresized: function () {
setTimeout(updateLabels, 200);
}
});
var rightlabel = d3.select("#chart svg g")
.append("text")
.text("i'm a label in the green region").style("fill", "green")
.attr("transform","translate(" + (chart.internal.x(3)) + ",20)")
.style("text-anchor","middle");
var leftlabel = d3.select("#chart svg g")
.append("text")
.text("i'm a label in the red region").style("fill", "red")
.attr("transform","translate(" + (chart.internal.x(1)) + ",20)")
.style("text-anchor","middle");
function updateLabels(){
rightlabel.attr("transform","translate(" + chart.internal.x(3) + ",20)");
leftlabel.attr("transform","translate(" + chart.internal.x(1) + ",20)");
}
chart.regions.add([{axis: 'x', end: 2, class: 'c3-region-r'},{axis: 'x', start: 2, class: 'c3-region-gr'}]);
.c3-region-gr{
fill: green;
fill-opacity: 0.1;
}
.c3-region-r{
fill: red;
fill-opacity: 0.1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<link href="https://rawgit.com/masayuki0812/c3/master/c3.css" rel="stylesheet"/>
<script src="https://rawgit.com/masayuki0812/c3/master/c3.js"></script>
<div id="chart"></div>
I'm starting with d3js and trying to make a graph on my own.
I'm trying to draw a curve between two points.
function CreateEdge(nodeId1,nodeId2,edgeLabel)
{
var curveData = [ { "x": 190, "y": 100}, { "x": 260, "y": 50} ];
var edge = d3.select("svg").append('g');
//diagonal function that can draw a curve goes in here
var curve = edge.append("path")
.attr("d", diagonal)
.attr("stroke", "#444")
.attr("stroke-width", 2)
.attr("fill", "none");
}
When i did my research i found some examples using diagonal function to draw curves. like this
Is there a way to use diagonal to draw a simple curve between two known points ? Or is there some alternative methods ?
You can do like this:
var curveData = [{ x: 190, y: 100 }, { x: 360, y: 150 }];
var edge = d3.select('svg').append('g');
var diagonal = d3.svg.diagonal()
.source(function (d) { return { x: d[0].y, y: d[0].x }; })
.target(function (d) { return { x: d[1].y, y: d[1].x }; })
.projection(function (d) { return [d.y, d.x]; });
d3.select('g')
.datum(curveData)
.append('path')
.attr('class', 'link')
.attr('d', diagonal)
.attr('stroke', '#444')
.attr('stroke-width', 2)
.attr('fill', 'none');
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.12/d3.js"></script>
</head>
<body>
<svg width=500 height=500></svg>
</body>
</html>