I have a working OOP code that recursively renders a composition of graphical elements to a canvas. There's quite a bit to dislike about it and I'm trying to see what a functional version will look like.
Sure, one can write a specialised recursive pure function, but as the framework involves similar algorithms, I'd like to:
Harness the power of function composition.
See how FP - and its data piping paradigm (transforming data through pure functions) - lands itself to more complex structures than lists (trees/graphs) and less trivial algorithms (than say, finding all odd number by sequentially iterating the list).
Inspired by Lazy.js, I've started coding and got this far:
LazyTree.from( drawing )
.keepNodes( visible )
.keepChildrenOf( nonClipping )
.traverse( log );
But as for map and fold - I have many unanswered questions.
Goal
Here's a simplified version of the problem I'm trying to solve:
Data
A composition (hierarchy) of rectangles. The bounds of each are in relative coordinates (to its parent):
const drawing = {
name: 'Face',
bounds: { x: 10, y: 10, w: 100, h: 100 },
children: [{
name: 'Left eye',
bounds: { x: 10, y: 10, w: 20, h: 20 }, // Abs: (20, 20, 20, 20)
children: [{
name: 'Left pupil',
bounds: { x: 5, y: 5, w: 10, h: 10 } // Abs: (25, 25, 10, 10)
}]
},{
name: 'Right eye',
bounds: { x: 70, y: 10, w: 20, h: 20 }, // Abs: (80, 20, 20, 20)
children: [{
name: 'Right pupil',
bounds: { x: 5, y: 5, w: 10, h: 10 } // Abs: (85, 25, 10, 10)
}]
}]
};
Task - getAbsoluteBounds
The task is to convert this composition to one that has absolute coordinates (as shown in the comments).
Issues and thoughts
Fold?
The absolute coordinates of a child is its relative coordinates transposed by its parent absolute coordinates. So a fold with its accumulator are candidates to do this.
But fold is associated with catamorphism and verbs like combine, and typically returns a single value.
The transformation in question takes a tree and returns an identical structure but with different values - so it sounds more like a map, but one that needs an accumulator.
As far as the accumulator goes, it is worth noting that all the children of a specific node should get the same accumulator. For the data above, both Left eye and Right eye should get the same absolute coordinates of Face (as opposed to the Right eye getting the returned accumulator of Left eye in depth-first-traversal).
Another thing I'm not clear about is who should be in charge of constructing the output tree. Should it be the high-order functions (fold, map, or whatever), or should it be the aggregator?
Stop conditions
Related the the previous section, consider all rectangles to clip their children, and the following composition:
const drawing = {
name: 'Parent',
bounds: { x: 10, y: 10, w: 10, h: 10 },
children: [{
name: 'Child',
bounds: { x: 1000000, y: 1000000, w: 10, h: 10 },
children: [{
name: 'Grandchild',
bounds: { x: 5, y: 5, w: 5, h: 5 }
}]
}]
};
The Child bounds are out-of-bound with relation to its parent (Parent), so branch traversal should stop when traversing to Child (no point traversing to Grandchild).
The question is: How can this be implemented with a fold function? One solution is to stop branch traversal when the accumulator returns an agreed valued (say undefined). But this is somewhat a departure from the fold API for lists.
Pre and post visit
The rendering algorithm involves:
fill( shape );
renderChildren( shape );
stroke( shape );
I wonder how this can be achieved with something like traverse() or each(). Should these take 2 callbacks (pre, post)?
Traversal strategies
Tree traversal may be:
Depth or Breadth first.
Top-down or Bottom-up (for the latter, see a specialised example for transforming an AST, using reduce).
With lists, we have functions like reverse(). Lazy.js allows adding a custom iterator that can then be chained.
So it seems the the FP way to handle traversal strategy is a transforming function. Is there anything else to it?
Summary
I've touched upon a few of the challenges in implementing a rendering algorithm for a tree structure using the data piping model.
I question if any other FP approaches would be more appropriate here? And perhaps the data piping model is not fit for these sort of problems. Or perhaps, I should simply forget the APIs one sees in FP libraries (that deal nearly exclusively lists) and create one that is appropriate for the task at hand (eg, having a map function that also involves an accumulator).
I couldn't find any FP library dedicated for trees, and information out there is typically limited to very simple problems.
So hopefully, someone would reply with something along the lines of 'this is how it should be done'.
As far as I have understood the details you might do as follows.
It will proceed traversing those items remaining within the parent's boundaries, converting their coordinates to absolute and rendering them just afterwards. However if the child's boundaries overlaps the parent's boundaries the child and it's descendants are skipped. No conversion to absolute coordinates and rendering are done for those.
function render(bounds){
console.log("Rendered:", bounds);
}
function relToAbs(o, b = {x: 0, y:0, w:Infinity, h:Infinity}, go = true){
go = o.bounds.x < b.w && o.bounds.y < b.h ? (o.bounds.x += b.x, o.bounds.y += b.y, render(o.bounds), go) : !go;
o.children && go && (o.children = o.children.map(p => relToAbs(p,o.bounds,go)));
return o;
}
var drawing = { name: 'Face',
bounds: { x: 10, y: 10, w: 100, h: 100 },
children: [{ name: 'Left eye',
bounds: { x: 200, y: 10, w: 20, h: 20 }, // Abs: (20, 20, 20, 20)
children: [{ name: 'Left pupil',
bounds: { x: 5, y: 5, w: 10, h: 10 } // Abs: (25, 25, 10, 10)
}]
},
{ name: 'Right eye',
bounds: { x: 70, y: 10, w: 20, h: 20 }, // Abs: (80, 20, 20, 20)
children: [{ name: 'Right pupil',
bounds: { x: 5, y: 5, w: 10, h: 10 } // Abs: (85, 25, 10, 10)
}]
}]
};
console.log(JSON.stringify(relToAbs(drawing),null,2));
Related
I have a bubble chart with multiple datasets. Two points of two different datasets may have the same coordinates (x and y-value) and lay on the same place in the chart. Because the display order of the points is determined according the order of the datasets, the smaller point could be completely covered by the bigger point in front of it.
Is there a option or a way, to display the points in order of their bubble size?
Simplified example of four points. The solution must also work for multiple datasets with each 30+ points.
I am searching a solution to draw the blue point in front of the red point, for the left pair and let the right pair as it is. This order must be independent of the order of the datasets, as it is per point and not per dataset.
Sorting the datasets seems to be no option for me, as the order cannot be determined per dataset, but instead must be determined for every coordinate/point. When drawing a point, it must be checked for this particular coordinate, if any other point with the same coordinates exists and if this point is greater than the current point (if true, the greater point must be drawn before, to not cover up the current point).
const config = {
type: 'bubble',
data: {
datasets: [{
label: 'Dataset 1',
data: [{
x: 1,
y: 1,
r: 20
},
{
x: 2,
y: 1,
r: 15
}
],
borderColor: 'red',
backgroundColor: 'red'
},
{
label: 'Dataset 2',
data: [{
x: 1,
y: 1,
r: 15
},
{
x: 2,
y: 1,
r: 20
}
],
borderColor: 'blue',
backgroundColor: 'blue'
}
]
},
options: {
responsive: true,
scales: {
x: {
suggestedMin: 0,
suggestedMax: 3
}
},
plugins: {
legend: {
position: 'top',
}
}
}
};
var ctx = document.getElementById('chartJSCanvas').getContext('2d');
const chart = new Chart(ctx, config);
<body>
<canvas id="chartJSCanvas" width="300" height="100"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.1.0/chart.js" integrity="sha512-LlFvdZpYhQdASf4aZfSpmyHD6+waYVfJRwfJrBgki7/Uh+TXMLFYcKMRim65+o3lFsfk20vrK9sJDute7BUAUw==" crossorigin="anonymous"></script>
</body>
The easiest way would be to just sort the data in the datasets and then the datasets themselves before drawing them.
An easy way to do this is provided by Array.prototype.forEach and Array.prototype.sort
First sort the data within each dataset like this:
config.data.datasets.forEach(function(element){
element.data.sort(function (a, b) {
return a.r - b.r;
});
});
Then you can sort the data sets by their smallest element like this:
config.data.datasets.sort(function (a, b) {
return a.data[0].r - b.data[0].r;
});
After that, you can regularly pass your config object with ordered datasets to your library call just the way you do it above:
const chart = new Chart(ctx, config);
Been struggling to find this, and have even resorted to logging output of echarts' various objects in the console to try to see what's going on. It would seem that visualMap doesn't work properly when you specify a different dimension in your data, so I've been trying to write a simple renderItem function for my line series. I can't find any documentation or examples on specifics for rendering a line - specifically, when data is passed into it, how do I get coordinate points for both the start and end of the line? I can get the values of the point (the data point which would give you the coordinates of the current data point), but a line consists of 2 points (start and end). What am I missing here?
Codepen example to play with: https://codepen.io/WorldWideWebb/pen/JjbJpRJ
visualMap that didn't work (it changes the color of the point, NOT the line; the point colors do change just fine, but I want to color the line)
visualMap: {
dimension: 3,
seriesIndex: 2,
show: false,
pieces: [{
gt: 0,
lte: 50,
color: '#93CE07'
}, {
gt: 50,
lte: 100,
color: '#FBDB0F'
}, {
gt: 100,
lte: 150,
color: '#FC7D02'
}, {
gt: 150,
lte: 200,
color: '#FD0100'
}, {
gt: 200,
lte: 300,
color: '#AA069F'
}, {
gt: 300,
color: '#AC3B2A'
}],
outOfRange: {
color: '#999'
}
},
You can see the points in the different colors if you uncomment the visualMap in the codepen example and change "showSymbol" in the last item in the series to true.
This one was close, but I haven't been able to figure out how to get both the origin and end to create the line properly (x2 and y2 are both static); also, this seems to produce a bunch of separate lines, rather than a line series:
renderItem: function (params, api) {
var coord = api.coord([api.value(0), api.value(1)]);
return {
type: 'line',
shape: {
x1: coord[0],
y1: coord[1],
x2: 200,
y2: 100
},
style: { stroke: formatDirectionLabel(api.value(1)), lineWidth: 2 }
}
}
Example of the data (this is time, wind speed, wind gust, wind direction, and temp); I use map to transform that into simple arrays when it's pulled:
{"dt":"2021-02-18 06:33:10","w":"7.38","g":"9.84","dir":"343","f":"47.70"}
My goal is to display a line - time is the X axis, wind speed is the y axis, and color the line based on wind direction. Like I said, all of it would work if I just wanted to color the points, but that would make the graph really cluttered (not what I'm going for).
How do I color each line segment based on wind direction? Can someone provide a simple example of passing data in to render a custom line using renderItem?
With a whole bunch of trial and error, I got it working. The solution was using params.dataIndexInside+1 to produce the next item in the series. renderItem contents:
let coord1 = api.coord([api.value(0, params.dataIndexInside), api.value(1, params.dataIndexInside)]),
coord2 = api.coord([api.value(0, params.dataIndexInside+1), api.value(1, params.dataIndexInside+1)]);
return {
type: 'line',
shape: {
x1: coord1[0],
y1: coord1[1],
x2: coord2[0],
y2: coord2[1]
},
style: {
stroke: customFormattingFunction(api.value(3))
}
},
Hope that saves someone some time
Issue
I'm using json data from jira to render a project's burndown chart. For reasons I won't go into, I can't use the built-in Jira Agile charts. I'm using chartist.js to render the burndown but I'm running into troubles and would appreciate a chartist.js user's input.
I've attached an image of the chart I want to generate as a reference.
Problem
Burndown events happen more than once during the day. Each has its own time that it happened. I don't want to show the specific time each happened in the x axis label group. Is there a way to "group" the events for a given day on a specific day, but show that they didn't all happen at once?
I want to create a burndown "guideline" (see the grey line in the attached image). Is there an easy way to have a line start from the top and finish at the bottom for a given time?
Asides
I've been enjoying using chartist.js, but I'm not married to it for my project, I can use any chart library I want. I would gladly accept suggestions for another charting library that does what I need it to.
Although Chartist does not directly support a convenient API for creating time based charts (this will land very soon), we have added dynamic axis configuration which allows you to switch the standard X axis type (step based) to a more sophisticated scale axis.
As Dates will be converted to numbers and the math behind the scene is the same, you can very easily create time based charts with a bit of manual work. We have no tick generator based on time frames as of yet but again this will come with the TimeAxis that will be created very soon in Chartist.
To get more information on the custom axis configuration you can read the docs here: http://gionkunz.github.io/chartist-js/getting-started.html#switching-axis-type
To show you and others how easy it would be to implement a burndown chart I've created one for you with Chartist 0.9.1. I'm using moment.js to format the dates.
Here is the jsbin: http://jsbin.com/lokozu/edit?html,js,output
var chart = new Chartist.Line('.ct-chart', {
series: [{
name: 'remaining',
data: [
{x: new Date(143134652600), y: 53},
{x: new Date(143334652600), y: 40},
{x: new Date(143354652600), y: 45},
{x: new Date(143356652600), y: 41},
{x: new Date(143366652600), y: 40},
{x: new Date(143368652600), y: 38},
{x: new Date(143378652600), y: 34},
{x: new Date(143568652600), y: 32},
{x: new Date(143569652600), y: 18},
{x: new Date(143579652600), y: 11}
]
}, {
name: 'stories',
data: [
{x: new Date(143134652600), y: 53},
{x: new Date(143334652600), y: 30},
{x: new Date(143384652600), y: 30},
{x: new Date(143568652600), y: 10}
]
}]
}, {
axisX: {
type: Chartist.FixedScaleAxis,
divisor: 5,
labelInterpolationFnc: function(value) {
return moment(value).format('MMM D');
}
},
axisY: {
onlyInteger: true,
low: 0
},
series: {
remaining: {
lineSmooth: Chartist.Interpolation.step(),
showPoint: false
},
stories: {
lineSmooth: false
}
}
});
I have a columnrange series in my highchart, and I want to be able to set a specific color for each range in a series. As long as I just used one color, this worked fine:
data: [[1, 5, 10], [2, 10, 20]]
Here the first value in each list is the X and the next two is the start and the end of the Y range. In order to have a separate color for each range I figured I needed to use the "object notation" instead of the array notation, so I tried this:
data: [{
x: 1,
y: [5,10],
fillColor: "red"
},{
x: 2,
y: [10,20],
fillColor: "blue"
}]
This did however not work (got invalid values). I cannot see that this case is covered by the API doc: http://api.highcharts.com/highcharts#series.data
Does anyone know how I can have ranges (two Y values) with the object notation, or if there is another way I can color the ranges differently? PS: My Y values are actually date ranges, but I just simplified it for the example above.
Edit: Here is a jsfiddle showing what I try to achieve: http://jsfiddle.net/Q2JMF/1/ . If you change the type to "column", and change the y-values to just single integers, you see that it works fine. But with columnrange I cannot get it working.
Proper options are low and high, see: http://jsfiddle.net/Q2JMF/2/
data: [{
x: 1,
low: 7,
high: 8,
color: "red"
}, {
x: 2,
low: 6,
high: 7,
color: "blue"
}]
For example, I need to record variables for every particle in a system for every frame for say 3000 frames.
Data is like:
{
acc: {
x: 0,
y: 0
},
vel: {
x: -0.33632,
y: -0.13723
},
loc: {
x: 410.88289,
y: 132.34004
},
r: 30,
maxSpeed: 1.03844,
maxForce: 0.04844,
head: Path#4,
subtilinCount: 0,
count: 0,
lastRot: -157.80223,
producing: false,
subtilinSize: 5,
category: 'predator',
life: 1706.71235,
replicateRate: 1,
birth: 372.29947,
antigen: {
life: 29,
loc: {
x: 411.21921,
y: 132.47728
},
radius: 60,
subtilin: PlacedSymbol#3563,
category: 'predator'
},
concentrationPrey: 1,
concentrationPredator: 3,
lastLoc: {
x: 411.21921,
y: 132.47728
}
}
I have looked at html 5 local storage although this doesnt seem to be the best fit. Since there are about 500 particles and 60 frames per second, sending it to the server whilst the simulation is running does not seem feasible.
Any ideas?