How to click on a D3 (svg) object to open a tiddler ?

409 views
Skip to first unread message

Silverfox

unread,
Apr 2, 2018, 5:45:32 AM4/2/18
to tiddl...@googlegroups.com
Hello, 

Following this discussion (https://groups.google.com/forum/#!topic/TiddlyWiki/MIm5WyIZIhg) I go on working on using D3 plugin to navigate into TiddlyWiki.

In this basic example http://d3jsv5trial.tiddlyspot.com/#D3%20circle%20pack%20with%20clickable%20feature when we double click on a bubble it opens an alert with the name of the bubble.

 I would like it to open a tiddler with the name of the bubble, and I did not found what could be the instruction for that.

my current code is : 

.on("dblclick",function(d){ alert("I'd like to open tiddler: "+ d.data.name); });

I was thinking about using foreignObject instruction, but I'm not sure this is the best way and I don't know how to implement it.

Do you have any recommandations that could help ?

Jed Carty

unread,
Apr 2, 2018, 5:56:49 AM4/2/18
to TiddlyWiki
This isn't directly related to d3 but it may have something that you can use as an example. I used SnapSVG instead of d3 but I have navigation and other buttons using it, hopefully it can be a bit useful to you.

I did some other experiments using SnapSVG to make an interface for tiddlywiki a few years ago and had some success with it before getting distracted. Somewhere I have a draggable navigation menu using it but I am not sure what the URL is right now.

BurningTreeC

unread,
Apr 2, 2018, 6:22:39 AM4/2/18
to TiddlyWiki
Hi @Silverfox 

you can invoke an action string you pass to your widget  ( <$d3widget actions="""<$action-navigate ... />""" /> ) with    "this.invokeActionString(actionstring,...,...,...,)" ... it's in widget.js
that would let you choose the actions you want to perform

or you can dispatch an event with "this.dispatchEvent(... ...... ..)" and pass your actions to it 


it's very useful to search for "dispatchEvent" and "invokeActionString" or "invokeActions" in the tiddlywiki advanced search, to see how the other widgets do it

BTC

Silverfox

unread,
May 12, 2018, 4:15:18 PM5/12/18
to TiddlyWiki
I'm still looking at a solution to make it work... but I'm stuck for many days with it

There are actually 2 different actions systems I don't know how to connect.

D3 has it own event detector with instruction like this one: 
.on("dblclick",function(d){ cliclic(d.data.name); });

and TiddlyWiki also with instruction like this
    this.dispatchEvent({
        type: "tm-navigate",
        navigateTo: targetTiddler });

In this last case it actually needs to be triggered by a button widget or something similar.
I was thinking about a way to activate a virtual click of the button to open the corresponding tiddler, but I have no idea how to proceed...
Maybe there some other better way to proceed...

In this example http://archipel.tiddlyspot.com double clicking on any bubble opens an alert with the name of the clicked bubble, but I would like to open a tiddler instead

Any help will be highly appreciated

Jed Carty

unread,
May 12, 2018, 4:31:50 PM5/12/18
to TiddlyWiki
Look at the handler inside the button widget for an example.


or you can look at the invokeActions and invokeActionString in widget.js starting on line 513


you can take those examples and use them as the handler functions.

Silverfox

unread,
May 13, 2018, 7:48:48 AM5/13/18
to tiddl...@googlegroups.com
My skills are definitely too limited...

Here is what I've done : 

D3 double click event activates a "cliclic" function 

in this function I wrote : 
    function cliclic(targetTiddler) {
//   alert("node " + targetTiddler + " was double clicked");
  console.log("targetTiddler",targetTiddler);
console.log("event",event.type);  
var d3Click = this.invokeActions(targetTiddler,event.type);                       
console.log("d3Click",d3Click);
}
 

But this returns a script error "Uncaught TypeError: Cannot read property 'invokeActions' of undefined"

I really do not catch how to use it all...

Jed Carty

unread,
May 13, 2018, 8:01:44 AM5/13/18
to TiddlyWiki
Now that I am looking at what I did with SnapSVG it isn't as simple as I had remembered.

The simplest way would be to make a widget based on the button widget, for the snapsvg plugin I made I created a new widget for it that was more general.

Unfortunately neither of those is quick and both probably require some in-depth knowledge of tiddlywiki. If you want to see an example here is the widget I made for SnapSVG:

Silverfox

unread,
May 13, 2018, 8:52:37 AM5/13/18
to TiddlyWiki
Wahoo... thank you but honestly I'm a bit lost...

BurningTreeC

unread,
May 13, 2018, 8:55:25 AM5/13/18
to TiddlyWiki
Hi Silverfox,

I think there are different ways one can accomplish what you want to do.

First, I think "this.invokeActions(...)" in your code should be "self.invokeActions(...)" and you should define a variable: var self = this on top of the scope:

YourWidget.prototype.render = function(parent,nextSibling) {
var self = this;

...

BurningTreeC

unread,
May 13, 2018, 8:58:42 AM5/13/18
to TiddlyWiki
... From within your clicclic function you can also:

self.dispatchEvent({type: "tm-navigate", navigateTo: targetTiddler)});

BurningTreeC

unread,
May 13, 2018, 9:03:45 AM5/13/18
to TiddlyWiki
I don't know if this works out-of-the-box but I'd try:

//this on top:
var self = this;

//here your code
//more code

//then this
.
on("dblclick",function(d){
var targetTiddler = d.data.name;
self.dispatchEvent({type: "tm-navigate", navigateTo: targetTiddler)});
});


dispatchEvent() is defined in widget.js and lets you dispatch an event from another widget ... look on tiddlywiki.com and search for "tm-" for a list of possible events you can dispatch and their parameters

BurningTreeC

unread,
May 13, 2018, 9:17:50 AM5/13/18
to TiddlyWiki
alternativelly, as I've looked at d3circlepackclick.js now where you have: monAction = "<$action-navigate $to='" +d.data.name +"'/>";

you can use:

self.invokeActionString(monAction,self,d,null);

instead of dispatchEvent() if you have an action string like your "monAction"

BurningTreeC

unread,
May 13, 2018, 9:23:09 AM5/13/18
to TiddlyWiki
... I use "invokeActionString" if I pass actions to the widget from within a tiddler like

<$mywidget actions="<$action-dosomething/>"/>

... and dispatchEvent() if I want to do what you're trying to accomplish.

just look at the widget.js tiddler. all the prototype functions are accessible from within your widget with "this.invokeActionString()", "this.dispatchEvent()" and so on.
you need to define the "self" variable which saves the "this" variable (if it's called variable - I don't know) - because "this" will be something different in your ".on('dblclick', function(e) { ... });" - but "self" will be the original "this"

BurningTreeC

unread,
May 13, 2018, 9:39:28 AM5/13/18
to TiddlyWiki
Silverfox, I've tested both variants and they both work.

This is your widget with my changes:

/*\
title: $:/plugins/tiddlywiki/d3/CirclePackWidget.js 
D3.js version: V5 plugin
type: application/javascript
module-type: widget

A widget for displaying Zoomable Circle Packing. Derived from https://bl.ocks.org/mbostock/7607535

\*/
(function(){

/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";


var Widget = require("$:/core/modules/widgets/widget.js").widget,
d3 = require("$:/plugins/tiddlywiki/d3/d3.js");


var CirclePackWidget = function(parseTreeNode,options) {
this.initialise(parseTreeNode,options);
};

/*
Inherit from the base widget class
*/
CirclePackWidget.prototype = new Widget();

/*
Render this widget into the DOM
*/
CirclePackWidget.prototype.render = function(parent,nextSibling) {
  var self = this; //added by BurningTreeC
// Save the parent dom node
this.parentDomNode = parent;
// Compute our attributes
this.computeAttributes();
// Execute our logic
this.execute();
// Create the chart
var chart = this.createChart(parent,nextSibling);
this.updateChart = chart.updateChart;
if(this.updateChart) {
this.updateChart();
}
// Insert the chart into the DOM and render any children
parent.insertBefore(chart.domNode,nextSibling);
this.domNodes.push(chart.domNode);
};

CirclePackWidget.prototype.createChart = function(parent,nextSibling) {
  var self = this; //added by BurningTreeC
// Get the data we're plotting
var root = this.wiki.getTiddlerData(this.myDataTiddler);
// Create SVG element
    var margin = {top: 20, right: 10, bottom: 20, left: 10},
        width = 800 - margin.left - margin.right,
        height = 800 - margin.top - margin.bottom;
var svg = d3.select(parent).insert("svg",function() {return nextSibling;})
        .attr("viewBox", "0 0 800 800")
        .attr("preserveAspectRatio", "xMinYMin meet")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom);

var diameter = +svg.attr("width"),
g = svg.append("g").attr("transform", "translate(2,2)"),
format = d3.format(",d");

var pack = d3.pack()
.size([diameter - 4, diameter - 4]);
var monAction ="";

  root = d3.hierarchy(root)
      .sum(function(d) { return d.size; })
      .sort(function(a, b) { return b.value - a.value; });

  var node = g.selectAll(".node")
    .data(pack(root).descendants())
    .enter().append("g")
//   .attr("class", function(d) { return d.children ? "node" : "leaf node"; })
      .attr("fill", function(d) { return d.children ? "steelblue" : "#FFEBCC"; })
  .attr("fill-opacity", function(d) { return d.children ? ".3" : "1"; })
  .attr("stroke", "rgb(31, 119, 180)")
  .attr("stroke-width", "1px")
    .attr("stroke-opacity", ".5")
      .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
// .on("dblclick",function(d){ alert( "<$action-navigate $to='" +d.data.name +"'/>"); });
.on("dblclick",function(d){ monAction = "<$action-navigate $to='" +d.data.name +"'/>";
                                  self.dispatchEvent({type: "tm-navigate", navigateTo: d.data.name}); //added by BurningTreeC
                }); //was missing


  node.append("title")
      .text(function(d) { return d.data.name + "\n" + format(d.value); })

  

  node.append("circle")
      .attr("r", function(d) { return d.r; });

  node.filter(function(d) { return !d.children; }).append("text")
      .attr("dy", "0.3em")
      .text(function(d) { return d.data.name.substring(0, d.r / 3); })
  .attr("font", "arial")
  .attr("font-size","9px")
  .attr("fill","black")
  .attr("text-anchor","middle")
  ;
  
// Return the svg node
return {
domNode: svg._groups[0][0],
};

};


/*
Compute the internal state of the widget
*/
CirclePackWidget.prototype.execute = function() {
// Get the parameters from the attributes
this.myDataTiddler = this.getAttribute("data");
};

/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
CirclePackWidget.prototype.refresh = function(changedTiddlers) {
var changedAttributes = this.computeAttributes();
if(changedAttributes.data || changedTiddlers[this.myDataTiddler]) {
this.refreshSelf();
return true;
return false;
};

exports.d3circlepackclick = CirclePackWidget;

})();



Silverfox

unread,
May 13, 2018, 12:58:19 PM5/13/18
to TiddlyWiki
Yesss !

I'm so happy it works
Thank you so much for giving the solution and also for all the details that contribute to leverage my coding skill.

I can go on improving my plugin now... 

Nicolas
Reply all
Reply to author
Forward
0 new messages