I try to change the radius of the circles with class 'minima' in a bubble-plot from dimple.js. However, only stroke and opacity get updated, but not the size.
chart.draw();
d3.selectAll(".minima")
.style("stroke", "#000000") // works
.attr("opacity", "0.2") // works
.attr("r", 25); // doesn't work
I don't see, what I am doing wrong here.
Background-info: I want only the 'minima-circles' to be bigger than the others without declaring a z-axis for the size, because I don't want this size-values to appear in the tooltip / pop-up.
Thanks for any idea!
Following from my comment above, this works but isn't very robust:
var svg = dimple.newSvg("body", 800, 600);
var data = [
{ "Word":"Hello", "Awesomeness":2000 },
{ "Word":"World", "Awesomeness":3000 }
];
var chart = new dimple.chart(svg, data);
chart.addCategoryAxis("x", "Word");
chart.addMeasureAxis("y", "Awesomeness");
var s = chart.addSeries("Word", dimple.plot.bubble);
chart.draw();
setTimeout(function () {
svg.selectAll("circle.Hello")
.attr("opacity", 0.2)
.attr("r", 100);
}, 100);
http://jsfiddle.net/T6ZDL/4/
Related
I'm trying to set the size of this globe to 200 x 200px.
I've learned that the projection is currently sized 960 x 500px.
Changing the size of the SVG doesn't shrink the globe. I'm having trouble understanding why.
Without luck I have tried to add the following to the code:
var width = 200;
var height = 200;
And
const width = 200;
const height = 200;
And
const svg = d3.select('svg')
.attr('width', 200).attr('height', 200);
How would I best approach this, and what am I doing wrong?
My code:
<!DOCTYPE html>
<html>
<body>
<svg></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script>
const width = 960;
const height = 500;
const config = {
speed: 0.005,
verticalTilt: -20,
horizontalTilt: 0
}
let locations = [];
const svg = d3.select('svg')
.attr('width', width).attr('height', height);
const markerGroup = svg.append('g');
const projection = d3.geoOrthographic();
const initialScale = projection.scale();
const path = d3.geoPath().projection(projection);
const center = [width/2, height/2];
drawGlobe();
drawGraticule();
enableRotation();
function drawGlobe() {
d3.queue()
.defer(d3.json, 'world-110m.json')
.defer(d3.json, 'locations.json')
.await((error, worldData, locationData) => {
svg.selectAll(".segment")
.data(topojson.feature(worldData, worldData.objects.countries).features)
.enter().append("path")
.attr("class", "segment")
.attr("d", path)
.style("stroke", "silver")
.style("stroke-width", "1px")
.style("fill", (d, i) => 'silver')
.style("opacity", ".5");
locations = locationData;
drawMarkers();
});
}
function drawGraticule() {
const graticule = d3.geoGraticule()
.step([10, 10]);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path)
.style("fill", "#fff")
.style("stroke", "#ececec");
}
function enableRotation() {
d3.timer(function (elapsed) {
projection.rotate([config.speed * elapsed - 120, config.verticalTilt, config.horizontalTilt]);
svg.selectAll("path").attr("d", path);
drawMarkers();
});
}
function drawMarkers() {
const markers = markerGroup.selectAll('circle')
.data(locations);
markers
.enter()
.append('circle')
.merge(markers)
.attr('cx', d => projection([d.longitude, d.latitude])[0])
.attr('cy', d => projection([d.longitude, d.latitude])[1])
.attr('fill', d => {
const coordinate = [d.longitude, d.latitude];
gdistance = d3.geoDistance(coordinate, projection.invert(center));
return gdistance > 1.57 ? 'none' : 'tomato';
})
.attr('r', 7);
markerGroup.each(function () {
this.parentNode.appendChild(this);
});
}
</script>
</body>
</html>
Projection.scale()
The scale of the projection determines the size of the projected world. Generally speaking d3 projections have a default scale value that will fill a 960x500 SVG/Canvas. A map produced with d3.geoOrthographic doesn't have a long edge, so this is 500x500 pixels. The default scale value is: 249.5 - half the width/height (allowing for stroke width). This scale factor is linear on both width and height: double it and double both (quadruple projected size of world). So if you want a 200x200 px world you'll want: 99.5 to be your scale value.
This is the default for d3.geoOrthographic, other scales have other scale defaults. For a Mercator, for example, it is 480/π: 2π of longitude across 960 pixels of width.
Projection.translate()
However, if you change the scale for a 200x200 pixel world, you'll have an issue with the default projection translate. By default this is set to [250,480] - half of [500,960], the default D3 anticipated size of the SVG/Canvas. This coordinate is where the geographic center of the projection (by default 0°N,0°W) is projected to. You'll want to change this to a value of [100,100]: the center of your SVG/Canvas.
Solution
const projection = d3.geoOrthographic()
.scale(99.5)
.translate([100,100]);
Automagic Solution
There is an easier way, but understanding the mechanics can be useful.
projection.fitSize()/.fitExtent() both set scale and translate automatically based on a specified width/height / extent. In your case this is easy to solve manually, but you could also use:
d3.geoOrthographic()
.fitSize([width,height],geoJsonObject)
or
d3.geoOrthographic()
.fitExtent([[left,top],[right,bottom]],geojsonObject)
As you're using topojson: topojson.feature returns a geojson object (with a features property containing individual features - the array of features can't be passed to fitSize or fitExtent).
I am trying to draw a vertical line in Dimple. I have seen this post: How to draw a vertical line with dimple?
However, the example provided there is not working for me. It is only adding a point where instead I'd like a line to be drawn. I cannot find anything in the docs, nor on StackOverflow, nor via Googling and I would think this should be an easy thing to do- but so far it has proven not to be. Any help would be appreciated.
Fiddle: http://jsfiddle.net/4w6gkq5s/27/
<!DOCTYPE html>
<html>
<body>
<div id="chartContainer">
<script src="http://dimplejs.org/lib/d3.v3.4.8.js"></script>
<script src="http://dimplejs.org/dist/dimple.v2.1.2.min.js"></script>
<script type="text/javascript">
var svg = dimple.newSvg("#chartContainer", 590, 400);
d3.tsv("http://dimplejs.org/data/example_data.tsv", function (data123) {
var data = [{'name': "name1", "percentChange": 120}, {'name': "name2", "percentChange": 80}, {'name': "name3", "percentChange": 100}];
var myChart = new dimple.chart(svg, data);
myChart.defaultColors = [ new dimple.color("#177c5f") ];
myChart.defaultColors = [
new dimple.color("#3d5739"), // green
];
var x2 = myChart.addMeasureAxis("x", "percentChange");
x2.title = "Percent of last month's sales"
var y = myChart.addCategoryAxis("y", "name");
y.addOrderRule("name");
y.title = null;
y.showGridLines = true;
/*Regional average*/
var y2 = myChart.addPctAxis("y", "Dummy");
var s3 = myChart.addSeries("Regional", dimple.plot.area, [x2, y2]);
s3.data = [{
"Regional": "Regional",
"Dummy": 1,
"percentChange": 105
}];
var s = myChart.addSeries(["percentChange", "name"], dimple.plot.bar);
s.barGap = .86;
// Set some custom display elements for each series shape
s.afterDraw = function (shape, data) {
var shape = d3.select(shape);
// Add some bar labels for the yValue
svg.append("text")
.attr("x", parseFloat(shape.attr("x")) + 40)
.attr("y", parseFloat(shape.attr("y")) - 5)
.style("text-anchor", "middle")
.style("font-size", "16px")
.style("fill", "#73b7e8")
.style("pointer-events", "none")
.text(data.cy);
};
s.addEventHandler("mouseover", onHover);
s.addEventHandler("mouseleave", onLeave);
myChart.draw();
function onHover(e) {
e.selectedShape[0][0].style.fill = "#00924f";
};
function onLeave(e) {
e.selectedShape[0][0].style.fill = "#3d5739";
};
});
</script>
</div>
</body>
</html>
That approach won't work with a measure axis on x. However the solution is actually much simpler in this case. After drawing you can add a line with a bit of d3:
svg.append("line")
.attr("x1", x._scale(105))
.attr("x2", x._scale(105))
.attr("y1", myChart._yPixels())
.attr("y2", myChart._yPixels() + myChart._heightPixels())
.style("stroke", "red")
.style("stroke-dasharray", "3");
Fiddle: http://jsfiddle.net/d3jo0uu5/1/
This uses a couple of the internal methods in dimple for height and width as well as the x axis scale for positioning the line horizontally. I've made it red and dashed but changing the style lines you can format it however you wish - or set a class instead and set the appearance in css.
There is probably a simple answer to this question... . I'm using d3 to create a globe, showing all countries. I also have a div with the name of all the countries in it. When I click on a country name, I want the globe to spin to that country. But I'm having trouble getting the syntax right. Can anyone help, please?
var feature;
var projection = d3.geo.azimuthal()
.scale(zoom)
.origin([-71.03,42.37])
.mode("orthographic")
.translate([380, 450]);
var circle = d3.geo.greatCircle()
.origin(projection.origin());
var scale = {
orthographic: 380,
stereographic: 380,
gnomonic: 380,
equidistant: 380 / Math.PI * 2,
equalarea: 380 / Math.SQRT2
};
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("#globe").append("svg:svg")
.attr("width", 800)
.attr("height", 800)
.on("dblclick", dblclick)
.on("mousedown", mousedown);
var g = svg.append("g");
d3.json("simplified.geojson", function(collection) {
g.append("g")
.attr("id", "countries")
g.append("g")
.selectAll("path")
.data(collection.features)
.enter().append("svg:path")
.attr("d", clip)
.attr("id", function(d) { return d.properties.ISO3; })
.attr("fill", function(d) { return d.properties.FILL; }) //change color and make clickable if data on this country exists
.on("mouseover", pathOver)
.on("mouseout", pathOut)
.on( "dblclick", dblclick)
.on("mousewheel.zoom", null)
.on("click", click);
feature = svg.selectAll("path");
feature.append("svg:title")
.text(function(d) { return d.properties.NAME; });
//here is where I want to be able to click a country name in the div and have the globe rotate to that country:
$('.represented').click(function(){
var countryabbrev = $(this).attr('id');
projection.origin(projection.invert(#path.centroid(#CAN))); //this line is wrong
refresh(1500);
showPerson(countryabbrev)
});
I've gotten it to find the country and rotate. Now the rotate is sketchy, but at least there's progress:
$('.represented').click(function(){
var countryabbrev = $(this).attr('id');
getCentroid(d3.select("#" + countryabbrev));
//projection.origin(projection.invert(#path.centroid(#CAN)));
projection.origin(getCentroid(d3.select("#" + countryabbrev)));
refresh(1500);
//showPerson(countryabbrev)
});
function getCentroid(selection) {
// get the DOM element from a D3 selection
// you could also use "this" inside .each()
var element = selection.node(),
// use the native SVG interface to get the bounding box
bbox = element.getBBox();
// return the center of the bounding box
return [bbox.x + bbox.width/2, bbox.y + bbox.height/2];
}
I'm trying to create a reusable pie chart with dynamic transitions as a learning task. I'm working off of the d3.js resuable components e-book by Chris Viau.
The problem I'm having is basically its not updating, but creating multiple pie charts. I'm wondering if I'm not understanding how d3.dispatch works or whether I've messed something up in the way the pie char should work. It creates multiple circles instead of dynamically updating a single pie chart with random values.
here is my jsfiddle.
http://jsfiddle.net/seoulbrother/Upcr5/
thanks!
js code below:
d3.edge = {};
d3.edge.donut = function module() {
var width = 460,
height = 300,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var dispatch = d3.dispatch("customHover");
function graph(_selection) {
_selection.each(function(_data) {
var pie = d3.layout.pie()
.value(function(_data) { return _data; })
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 100)
.outerRadius(radius - 50);
if (!svg){
var svg = d3.select(this).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
}
var path = svg.selectAll("path")
.data(pie)
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc)
.each(function(d) {this._current = d;} );
path.transition()
.ease("elastic")
.duration(750)
.attrTween("d", arcTween);
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
});
}
d3.rebind(graph, dispatch, "on");
return graph;
}
donut = d3.edge.donut();
var data = [1, 2, 3, 4];
var container = d3.select("#viz").datum(data).call(donut);
function update(_data) {
data = d3.range(~~(Math.random() * 20)).map(function(d, i) {
return ~~(Math.random() * 100);
});
container.datum(data).transition().ease("linear").call(donut);
}
update();
setTimeout( update, 1000);
The main reason for multiple SVGs appearing is that you're not checking if there is one already correctly. You're relying on the variable svg being defined, but define it only after checking whether it is defined.
The better way is to select the element you're looking for and check whether that selection is empty:
var svg = d3.select(this).select("svg > g");
if (svg.empty()){ // etc
In addition, you need to handle the update and exit selections in your code in addition to the enter selection. Complete jsfiddle here.
I'm trying to highlight some points in a time series modeled using a nvd3.js eventLineChart - more precisely I have a json object with time-stamps and for each time-stamp I would like to add a vertical line at this particular date/time. The highlighted points may not exist in the time-series data source and are global over all groups of the time-series data (like ticks).
Any ideas on how this could be achieved? - I tried adding a standard line to my plot (fixed y1 and y2 and x according to the timestamp of the events i want to highlight) but wasn't able to have the timestamps scaled to the same range as the original time series.
Here are some parts of the model I started to build for that purpose based on nv.models.lineChart. - (just an excerpt of the model as most of the code is just a copy from the lineChart model):
nv.models.eventLineChart = function() {
// adds vertical highlights to line chart
"use strict";
var chartEvents = {}
function chart(selection) {
selection.each(function(data) {
// Setup Scales
x = lines.xScale();
y = lines.yScale();
// Setup containers and skeleton of chart
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
var g = wrap.select('g');
gEnter.append('g').attr('class', 'nv-eventLinesWrap');
//------------------------------------------------------------
// Main Chart Component(s)
var eventWrap = wrap
.select('.nv-eventLinesWrap')
.selectAll('.nv-eventLines')
.data(function(d) {return d });
eventWrap
.enter()
.append('g')
.attr('class', 'nv-eventLines');
// chartEvents json ==> [{decsription: "test,"timestamp: 1375031820000}]
var eventLine = eventWrap
.selectAll('.nv-eventLine')
.data(chartEvents, function(d){return (d.timestamp)});
var eventLineEnter = eventLine.enter()
.append('line').attr('class', 'nv-eventLine')
.style('stroke-opacity', 0);
// #todo set ymin and ymax
eventLine
.attr('x1', function(d){ return x(d.timestamp);})
.attr('x2', function(d){ return x(d.timestamp);})
.attr('y1', 300)
.attr('y2', 800)
.style('stroke', function(d,i,j) { return color(d,j) })
.style('stroke-opacity', function(d,i) {
return 1;
});
});
return chart;
}
chart.setChartEvents = function(_) {
if (!arguments.length) return chartEvents;
chartEvents = _;
return chart;
};
return chart;}
This model is called by using:
nv.addGraph(function() {
var nv3dChart = nv.models.eventLineChart().useInteractiveGuideline(true).setChartEvents(json.chartEvents);
// json.chartEvents >> [{decsription: "EventDescription,"timestamp: 1375031820000}]
nv3dChart.xAxis
.showMaxMin(false);
.tickFormat(function(d) { return d3.time.format("%Y-%m-%d")(new Date(d)) });
nv3dChart.yAxis
.axisLabel(widgetConfig.action.data.kpiName)
.tickFormat(d3.format(',.f'));
var ndg = d3.select(renderToElementId+' svg');
ndg.datum([{
values: json.data,
key: widgetConfig.action.data.tagName
}])
.transition().duration(500);
nv.utils.windowResize(nv3dChart.update);
return nv3dChart;})
Which produces currently this svg output (events that should be displayed by vertical lines only)
<g class="nv-eventLines">
<line class="nv-eventLine" x1="1375031820000" x2="1375031820000" y1="300" y2="800" style="stroke: #1f77b4;"></line>
</g>
.. as described I haven't yet figured out a way to implement the scaling of the events x values according to the scale of the line chart
Would greatly appreciate any help regarding this problem
I now manually created all scales for x and y and added them to the nvd3 elements. I'm not particularly happy with that solution as it prevents me from creating a more modular feature for multiple nvd3 charts but it is a starting point.
Here is an outline of my current solution:
nv.models.eventLineChart = function() {
// initialize scales
var y = d3.scale.linear(),
x = d3.scale.linear();
// set scales of lines
lines = nv.models.line()
.yScale(y)
function chart(selection) {
//#todo support for multiple series
// set domain and range for scales
x
.domain(d3.extent(data[0].values, function(d){return d.x}))
.range([0,availableWidth]);
y
.domain(d3.extent(data[0].values, function(d){return d.y}))
.range([0,availableHeight]);
// add container for vertical lines
gEnter.append('g').attr('class', 'nv-eventLinesWrap');
// setup container
var eventWrap = wrap.select('.nv-eventLinesWrap').selectAll('.nv-eventLines')
.data(function(d) {return d });
eventWrap.enter().append('g').attr('class', 'nv-eventLines');
eventWrap.select('.nv-eventLinesWrap').attr('transform', 'translate(0,' + (-margin.top) +')');
var eventLine = eventWrap.selectAll('.nv-eventLine').data(chartEvents, function(d){return (d.timestamp)});
var eventLineEnter = eventLine.enter()
.append('line').attr('class', 'nv-eventLine')
// configure and style vertical lines
//#todo: set color for lines
eventLine
.attr('x1', function(d){ return x(d.timestamp)})
.attr('x2', function(d){ return x(d.timestamp)})
.attr('y1', y.range()[0])
.attr('y2', y.range()[1])
.style('stroke', function(d,i,j) { return "#000"; })
.style('stroke-width',1)
// #todo add vertical lines via transitions, add vLine content to toolbox
}}
Thank you, Lars, for your contributions .. they really helped a lot to understand certain parts in more detail.
If anyone has come up with a better idea to solve this problem I would be very grateful if you could post these suggestions here!