Browser: Dartium Version 34.0.1847.0 (258268)
I want to embed D3 inside Polymer.dart elements and I'm just beginning to sort out how Dart and JS interoperate. I started by creating a vanilla HTML page using HTML5/CSS3/JS from D3's introductory tutorial on how to make a bar chart to be sure my D3 implementation worked in Dartium.
I then moved the D3 code into a custom polymer element (shown below) and its companion dart source file (not shown). How do I instruct D3 to perform its selection inside the shadow DOM?
<!-- D3 code copied from: http://bost.ocks.org/mike/bar/ -->
<polymer-element name="dart-js">
<template>
<!-- D3 style -->
<style>
.chart div {
font: 10px sans-serif;
background-color: steelblue;
text-align: right;
padding: 3px;
margin: 1px;
color: white;
}
</style>
<content>
<!-- D3 container -->
<div class="chart">inside dart-js shadow-root</div>
<!-- D3 JS -->
<script type="application/javascript">
console.log("inside content");
var data = [4, 8, 15, 16, 23, 42];
console.log("data: " + data); // data: 4,8,15,16,23,42
console.log("select: <" + d3.select(".chart") + ">"); // select: <>
// How do I instruct d3 to search inside the shadow DOM?
d3.select(".chart")
.selectAll("div")
.data(data)
.enter().append("div")
.style("width", function (d) { return d * 10 + "px"; })
.text(function (d) { return d; })
</script>
</content>
</template>
<script type="application/dart;component=1" src="dart_js.dart"></script>
</polymer-element>
d3 can't find elements inside a shadowDOM but you look up the element in Dart and pass it to d3's select method.
something like this (not tested)
import 'dart:js' as js;
js.context['d3'].callMethod('select', [shadowRoot.querySelector('.chart')]);
more info about Dart-JS-interop:
https://www.dartlang.org/articles/js-dart-interop/
I wrapped the D3 JS fragment that prints the bar chart in a function and invoked it from Dart as follows:
dart-js.html
var x = function(elt) {
console.log('x');
d3.select(elt)
.selectAll("div")
.data(data)
.enter().append("div")
.style("width", function (d) { return d * 10 + "px"; })
.text(function (d) { return d; });
};
dart_js.dart
import 'package:polymer/polymer.dart';
import 'dart:js' as js;
#CustomTag('dart-js')
class DartJs extends PolymerElement {
/* Life cycle events */
// required -- constructor must call superclass
DartJs.created() : super.created() {
print("dart-js started");
}
#override void enteredView() {
super.enteredView();
print('dart_js entered');
js.context.callMethod('x', [shadowRoot.querySelector('.chart')]);
}
}
Related
I am trying to integrate Vue.js and D3.js. What I notice is sometimes the CSS classes don't really work on the svg elements. I am giving the snippet of the vue component below.
<template>
<div>
<h1>Hello world</h1>
<svg width="300" height="100" ref="barchart"></svg>
</div>
</template>
<script>
import * as d3 from "d3";
export default {
name: "LineChart",
mounted() {
d3.select("h1").attr("class","red-text")
var data = [10,20,15,30,60];
var barHeight = 20;
var bar = d3
.select(this.$refs.barchart)
.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("class","rect")
.attr("width", function(d) {
return d;
})
.attr("height", barHeight - 1)
.attr("transform", function(d, i) {
return "translate(0," + i * barHeight + ")";
});
}
};
</script>
<style scoped>
.rect{
fill: blue;
}
.red-text{
color:red;
}
</style>
Its output is obtained as :-
scoped css output
But as sson as I remove the scoped attribute, the code works fine. New output :-
global css output
Thanks in advance!
Scoped styles work by vue assigning a unique attribute to dom elements, and then adjusting the style rules by adding an extra criteria for elements to have that attribute. Example in vue guide. However, since elements dynamically created with d3 aren't managed by vue (since they aren't part of the template), it doesn't work out of the box. One way to solve this, is to to use deep selector (e.g. svg >>> .rect { ... }), which doesn't attach the additional unique attribute criteria for the child elements.
If you just want to color the bars you don't need explicit css. You can just set:
.style("fill", function(d) { return 'blue'; })
on your bar.
I would like to add tooltips for a project previously discussed here:
How to apply specific colors to D3.js map based on data values?
I've added a style for the tooltip-container as follows:
<style>
#tooltip-container{
background: #eee;
box-shadow: 0 0 5px #999999;
color: #333;
display: none;
font-size: 12px;
padding: 10px;
position: absolute;
text-align: center;
right: 200px;
top: 300px;
width: 140px;
height: 230px;
z-index: 10;
}
</style>
I also added the html tooltip container in a div:
<div id="tooltip-container"></div>
I've revised my drawMap() function as follows:
function drawMap(conus) {
svg.selectAll(".feature")
.data(conus.features)
.enter().append("path")
.attr("class", "county")
.attr("id", function (d) { return d.properties.ID_1; }, true)
.attr("d", path)
.on("mouseover", function (d) {
$("#tooltip-container").show();
})
.on("mouseout", function () {
$("#tooltip-container").hide();
});
//Fill county colors
var dataRange = getDataRange();
d3.selectAll('.county')
.attr('fill', function (d) {
return getColor(d.properties[attributeArray[currentAttribute]], dataRange);
});
}
What I need to do now are as follows:
When a user clicks the STOP button, the tooltip-container should display the appropriate warning text for that day from the "warnings.csv" file. The csv file can be downloaded from here: https://nlet.brc.tamus.edu/Home/Swat) by selecting the Management tab in the SWAT download section and selecting the "Warnings csv file".
Then, as users hover over the map, the tooltip-container should switch to that county and display the appropriate warning for the county for that day.
Appreciate any help. Thanks in advance.
Never mind...solved this. I revised the following functions from this project:
<div id="tooltip-container"></div>
function drawMap(conus) {
var curr = $("#clock")[0].innerText;
var day = curr.charAt(0);
svg.selectAll(".feature") // select country objects (which don't exist yet)
.data(conus.features) // bind data to these non-existent objects
.enter().append("path") // prepare data to be appended to paths
.attr("class", "county") // give them a class for styling and access later
.attr("id", function (d) { return d.properties.ID_1; }, true) // give each a unique id for access later
.attr("d", path) // create them using the svg path generator defined above
.on("mouseover", function (d) {
$("#tooltip-container").show();
loadWarning(d, day);
})
.on("mouseout", function (d) {
$("#tooltip-container").hide();
});
var dataRange = getDataRange(); // get the min/max values from the current day's range of data values
d3.selectAll('.county')
.attr('fill', function (d) {
return getColor(d.properties[attributeArray[currentAttribute]], dataRange);
});
}
function loadWarning(d, day)
{
var html = "";
var tooltip = d3.select("#tooltip-container");
d3.csv("/data/warnings2.csv", function (error, data) {
for (var i in data) {
if (data[i].id == d.properties.ID_1) {
html += "<table><tr><strong>" + d.properties.County + "</strong></tr><br/>" +
"<tr>ID: " + d.properties.ID_1 + "</tr><br/><br/>" +
"<tr><div style='text-align:left'>" + data[i][day] + "</div></tr></table>";
tooltip.html(html);
}
}
});
}
Using d3.behavior.drag(), is there a way to enable a dragged image to be visible outside of its parent svg element borders.
In my app, I have a top layout based on an HTML grid (using flexBox) and several D3.js graphs located in each grid cell. Each graph is built with an SVG element and its childrens.
I need a drag and drop feature to enable copy/move of elements between these graphs. As now, the feature is working except that the drag image disappears when I cross the border of the source graph.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
float: left;
border-bottom: solid 1px #ccc;
border-right: solid 1px #ccc;
margin-right: -1px;
margin-bottom: -1px;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var width = 240,
height = 125,
radius = 20;
var overSVG;
var drag = d3.behavior.drag()
.origin(function (d) {
return d;
})
.on("drag", dragmove)
.on("dragend", dragend);
var svg = d3.select("body").append("div").selectAll("svg")
.data(d3.range(2).map(function (v, i) {
return {
svgElement: i,
x : width / 2,
y : height / 2
};
}))
.enter().append("svg")
.attr("width", width)
.attr("height", height)
.attr("id", function (d, i) {
return "svg_" + i
})
.on("mouseover", over)
.on("mouseout", out);
svg.append("circle")
.attr("r", radius)
.attr("cx", function (d) {
return d.x;
})
.attr("cy", function (d) {
return d.y;
})
.attr("svgElement", function (d, i) {
return i;
})
.call(drag);
function over(d, i) {
overSVG = d;
var selectedNodeId = "#svg_" + i;
d3.select(selectedNodeId)
.attr("fill", 'red');
}
function out(d, i) {
overSVG = "";
var selectedNodeId = "#svg_" + i;
d3.select(selectedNodeId)
.attr("fill", 'blue');
}
function dragmove(d) {
d3.select(this)
.attr("cx", d.x = d3.event.x)
.attr("cy", d.y = d3.event.y);
console.log("drag move", this, d3.event.x, ' ', d3.event.y);
}
function dragend(d) {
console.log("==>drag Ended :");
console.log(" dragged circle", this);
console.log(" from svg ", d);
console.log(" to", overSVG);
}
</script>
By default the svg elements have an overflow attribute set to hidden.
You can try setting the overflow attribute to visible
svg {
overflow: visible;
}
Again, without seeing a working example, it's hard to tell if this will work for you, but maybe it can help.
I am trying to access particular variable from a csv file as shown below:
Month,Year,leaves
Jan,2011, 20
Feb, 2011, 30
Mar,2011, 40
What I am trying to achieve is to create a bar chart with height being the leaves value. Below is the code that I used to access leaves field from the csv file imported. I am doing something wrong here, as I am new to D3.js I am pretty confused about accessing an object or referencing an object (syntax in general). I don't care about year, month, I am just trying to create a simple bar chart with leaves. Any help or pointer towards valuable resources would be much appreciated.
Thanks
<!DOCTYPE HTML>
<html>
<head>
<script type="text/javascript" src="d3.min.js"></script>
<h4> D3 Bar Chart </h4>
</head>
<style>
.bar{
display:inline-block;
width: 20px;
height: 80px;
margin-right: 2px;
background-color: teal;
}
</style>
<body>
<script>
d3.csv("sl_month_year.csv", function(error, data)
{ if(error) {
console.log(error);}
else {console.log(data);
var bar = d3.selectAll("body")
.select("div")
.data(data.length)
.enter()
.append("div")
.attr("class","bar")
.style("height", function(d) { for (i=0; i <= d.length; i++) { return d.[i].leaves + "px" ;});
});
I see a few things that seem wrong to me in your code.
First, you try to access a property using d.[i].leaves in your loop; I think what you try to do is d[i].leaves.
Second, you do not actually need to loop on the elements in your collection, as d3 will handle that for you.
I would go for a different way of using selections, using a simple select for the body and a selectAll for the div's, and bind to the data directly instead of data.length.
Code below should be fine (untested though):
d3.csv("sl_month_year.csv", function(error, data){
if(error) {
console.log(error);}
else {
console.log(data);
var bar = d3.select("body")
.selectAll("div")
.data(data)
.enter()
.append("div")
.attr("class","bar")
.style("height", function(d) {
return d.leaves + "px" ;
});
});
I've tried this all day long and haven't got it. I would like to display a string of text in some delaying fashion. For example, at first it displays "a" then waits for a second then display "ab", and then waits for a second then display "abc", so far so on ...
I use D3 to display, function slice to generate partial text string from the alphabet. I use either setTimeout or setInterval. None works. I appreciate some help. Here is my code:
<!DOCTYPE html>
<html>
<head>
<style>
text {
font: bold 48px monospace;
}
.enter {
fill: green;
}
.update {
fill: #333;
}
</style>
</head>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var alphabet = "abcdefghijklmnopqrstuvwxyz".split("");
var width = 1000,
height = 200;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(32," + (height / 2) + ")");
function update(data) {
var text = svg.selectAll("text").data(data);
text.attr("class", "update");
text.enter().append("text")
.attr("class", "enter")
.attr("x", function(d, i) { return i * 32; })
.attr("dy", ".35em");
text.text(function(d) { return d; });
text.exit().remove();
}
// Method 1 - NOT WORKING
update(alphabet.slice(0, 1));
setTimeout(function(){},3000)
update(alphabet.slice(0, 2));
setTimeout(function(){},3000)
update(alphabet.slice(0, 3));
// ...
/*/ Method 2 - NOT WORKING
var i = 1;
setInterval(function(i) {
update(alphabet.slice(0, i));
i++;
}, 1500);
*/
</script>
</body>
</html>
The update calls need to be in your setTimeout function, like:
setTimeout(function () {
update(alphabet.slice(0, 1));
}, 3000);
setTimeout is non-blocking; after the timer is up, it executes the function passed in as an argument.
Edit: You also probably want your code to be like this, removing the update function completely (maybe you have a reason for using many separate <text> elements?):
var label = svg.append("text");
var i = 1;
setInterval(function () {
label.text(alphabet.slice(0, i++).join(""));
}, 1500);