Control multiple fills and strokes of an SVG with a function - javascript

So I've built a map with several different colored lines. I chopped up the lines into a string of segments at each dot.
What I'm trying to do is use javascript to change the colors of the lines and eventually move to being able to change the dynamically with a feed.
I made a color chart that I'm trying to get the line segment object to find its color and change to the proper corresponding color. The dots pulsate the fill color with greensock properly but the lines are giving me trouble. I keep getting an object isn't a function error.
I've tried a few different ways to set up the formula but I'm stuck.
var colorSet = {
'#ff0000': '#ff6666',
'#008250': '#42a680',
'#a3238f': '#bd73b2',
'#0079c2': '#42a0db',
'#ff8c00': '#ffba66',
'#96c800': '#cae087',
'#a86000': '#c28f4e',
'#999999': '#cccccc',
'#ffe000': '#fff399'
}
var animateThis = function(obj) {
var getStroke = obj.getAttribute('stroke');
TweenLite.to(obj, 1.5, {
fill: "#bbbbbb",
yoyo: true,
repeat: -1
});
};
Here is my fiddle: https://jsfiddle.net/Spiderian/hzafm6vk/3/#&togetherjs=6e0qwPovaP

To color lines you need to use stroke, not fill. And on <line> not on <g>.
getStroke is not used in you code. (And there is no getAttribute on $ by the way. There is attr())
colorSet also unused, and I don't really understand what is it for. As all of lines and points in the jsfiddle you've provided are of the same color.
Don't really understand why there are two lines for each <g>.
But nevertheless here is what you need to do:
// change all line selectors from this
// const lsSIT0001 = $("#SIT0001");
// const lsSIT0002 = $("#SIT0002");
// ...
// to this
const lsSIT0001 = $("#SIT0001 line");
const lsSIT0002 = $("#SIT0002 line");
// ...
// because you need to change stroke color of `<line>`s, not `<g>`s
// split
// var itemsToAnimate = [ lsSIT0020, ..., lsSIT0001, SIR4947, ..., SIR4629 ]
// into
const linesToAnimate = [ lsSIT0020, ..., lsSIT0001 ]
const pointsToAnimate = [ SIR4947 , ..., SIR4629 ]
// run separate animations for lines and dots
const animatePoints = obj => TweenLite.to(obj, 1.5, { fill : "#ff0000", yoyo: true, repeat: -1 })
const animateLines = obj => TweenLite.to(obj, 1.5, { stroke: "#0000ff", yoyo: true, repeat: -1 })
linesToAnimate .forEach(animateLines )
pointsToAnimate.forEach(animatePoints)
Update:
To use colorSet you need to either
set style='stroke: some_color' on each <line> and then use it like this:
const style = obj.eq(i).attr('style')
// `i` is the index of a line inside <g> the color of which you want to get
TweenLite.to(obj, 1.5, { stroke: colorSet[style], ... })
In this case colorSet should look like
const colorSet = {
"stroke: #ff0000": '#ff6666',
...
"stroke: #ffe000": '#fff399',
}
or get computed styles with obj.get(i).computedStyleMap().get("stroke").toString()
In this case colorSet should look like
const colorSet = {
"rgb(255, 0, 0)": '#ff6666',
...
"rgb(255, 224, 0)": '#fff399',
}
But it's experimental technology. So probably don't :)
Or better yet - make a map not from color to color but from ids to color:
const colorSet = {
"#SIT0001": '#ff6666',
...
"#SIT0020": '#fff399',
}
Then you won't need to get colors from DOM.

Related

Phaser 3 - Check group collision with world bounds

In my scenario, i create a ship according to player's preferences. The ship consists of the flag and the hull.
An example view
PROBLEM-1
Ship is an Arcade.Group and i want to prevent this group from going outside the borders of the world.
create(){
// Create a group for ship
this.shipGroup = this.physics.add.group()
// Add hull to shipGroup
this.shipGroup.create(400, 500, "1021")
// Add flag to shipGroup
const mainFlag = this.shipGroup.create(400, 500, "FA1")
mainFlag.setOrigin(0.5, 0.8)
// Set collision property to true of every object in shipGroup
this.shipGroup.children.each((item: any) =>
item.setCollideWorldBounds(true)
)
}
update(t: number, dt: number){
if (this.cursor.up.isDown) {
this.shipGroup.setVelocity(...)
}
else {
this.shipGroup.setVelocity(0, 0)
}
}
With this approach every object in group calculated seperately. After hitting the world boundary, the position of the objects is distorted.
PROBLEM-2
To avoid this i tried another approach. I add bounding box to group. Instead of check collision for every object, i will only check collision for bounding box.
create(){
// Create a group for ship
this.shipGroup = this.physics.add.group()
// Add bounding box for shipGroup
this.shipBox = this.shipGroup.create(400, 500, "bbox")
this.shipBox.setCollideWorldBounds(true)
this.shipBox.body.onWorldBounds = true
// Add hull to shipGroup
this.shipGroup.create(400, 500, "1021")
// Add flag to shipGroup
const mainFlag = this.shipGroup.create(400, 500, "FA1")
mainFlag.setOrigin(0.5, 0.8)
/*this.shipGroup.children.each((item: any) =>
item.setCollideWorldBounds(true)
)*/
}
update(t: number, dt: number){
if (this.cursor.up.isDown && !this.shipBox.body.checkWorldBounds()) {
this.shipGroup.setVelocity(...)
}
else {
this.shipGroup.setVelocity(0, 0)
}
}
The problem is checkWorldBounds() returns false even if shipBox hits world boundaries. But collision for shipBox is work.
checkWorldBounds()
Description: Checks for collisions between this Body and the world
boundary and separates them.
Returns: True if this Body is colliding with the world boundary.
How can i implement collision for group and world boundary?
P.S. : phaser version is 3.55.2
There are a few ways to solve/work around this issue, I personally would just use only one image why one physics-body (hull and flag combined) and just move that single image/texture, and switch the image, when needed.
That said, if you need to use the separate images, the easy way is to use a phaser container. (link to the documentation)
create a container
add the images to the container
set the size for the container (default size is width=0 height=0)
create a physics body for the container
done
A short demo:
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536,
height: 183,
physics: {
default: 'arcade',
arcade: {
gravity:{ y: 0 },
debug: true
}
},
scene: {
create
},
banner: false
};
function create () {
this.add.text(10,10, 'Ship with physics')
.setScale(1.5)
.setOrigin(0)
.setStyle({fontStyle: 'bold', fontFamily: 'Arial'});
let graphics = this.make.graphics();
graphics.fillStyle(0xffffff);
graphics.fillRect(0, 0, 10, 40);
graphics.generateTexture('ship', 10, 40);
graphics.fillStyle(0xff0000);
graphics.fillRect(0, 0, 30, 10);
graphics.generateTexture('flag', 30, 10);
graphics.generateTexture('flag2', 20, 6);
let hull = this.add.image(0, 0, 'ship')
let flag = this.add.image(0, -5, 'flag')
let flag2 = this.add.image(0, 10, 'flag2')
this.ship = this.add.container(100, 80, [ hull, flag, flag2]);
this.ship.setAngle(-90)
this.ship.setSize(40, 30)
this.physics.world.enable(this.ship);
this.ship.body.setVelocity(100, 0).setBounce(1, 1).setCollideWorldBounds(true);
}
new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Info: this demo is based partly from this official example

How to set specific height for chartJs background color in terms of yAxis value

I have a line chart using chartJs see app.component.ts on Stackblitz
As you can see backgroundColor: 'rgba(250,174,50,1)' only gives color for whole chart.What I need is background color until a specific yAxis value.In this example for example until 200 I want to see orange color and rest is white like below.
Is there any way to do that?
The Plugin Core API offers a range of hooks that may be used for performing custom code. You can use the afterLayout hook to create a linear gradient through CanvasRenderingContext2D.createLinearGradient() and assign it to the backgroundColor property of the dataset.
plugins: [{
afterLayout: c => {
let dataset = c.data.datasets[0];
let thresholdValueIndex = dataset.data.indexOf(dataset.fillThreshold);
let xAxis = c.scales["x-axis-0"];
let xRight = xAxis.getPixelForTick(thresholdValueIndex);
let gradientFill = c.ctx.createLinearGradient(xAxis.left, 0, xRight, 0);
gradientFill.addColorStop(0, "rgb(250,174,50");
gradientFill.addColorStop(1, "rgb(250,174,50");
gradientFill.addColorStop(1, "white");
dataset.backgroundColor = gradientFill;
}
}],
This code expects the property fillThreshold to be defined on the dataset, its value must correspond to a value also contained in the data array.
Please take a look at your amended StackBlitz.

Set dynamic regions for each y axis group

I would like to implement an alternate region background to the chart, sort of like a striped table look.
Is there a way for me to dynamically set the regions for each y-axis block? Once I can do this, I can just basically use css to set alternating background colors.
The regions can be set statically like this:
http://c3js.org/samples/region.html
To do it dynamically, you'd just need to use the API:
http://c3js.org/reference.html#api-regions e.g.
chart.regions([
{axis: 'x', start: 5, class: 'regionX'},
{axis: 'y', end: 50, class: 'regionY'}
]);
So to get alternating stripes build up a regions array as so after generating your chart object:
// find range of all data
var allData = d3.merge (chart.data().map(function(d) {
return d.values.map (function(dd) { return dd.value; });
}));
var dataRange = d3.extent(allData);
dataRange.min = Math.min (dataRange[0], 0);
dataRange.extent = dataRange[1] - dataRange.min;
// set number of pairs of stripes
var stripeCount = 5;
var step = dataRange.extent / stripeCount;
var newRegions = [];
// then divide the data range neatly up into those pairs of stripes
d3.range(0,stripeCount).forEach (function(d) {
newRegions.push ({axis: 'y', start: dataRange.min+(step*d), end: dataRange.min+(step*(d+0.5)), class: 'stripe1'});
newRegions.push ({axis: 'y', start: dataRange.min+(step*(d+0.5)), end: dataRange.min+(step*(d+1)), class: 'stripe2'});
});
// set the new regions on the chart object
chart.regions(newRegions);
css:
.c3-region.stripe1 {
fill: #f00;
}
.c3-region.stripe2 {
fill: #0f0;
}
(If, alternatively, you wanted to make a new stripe pair every 10 units on the y scale you would just make step=10; and change the d3.range to d3.range(0,dataRange.extent/step))
I forked off someone's bar chart on jsfiddle and added the striping --> http://jsfiddle.net/k9c0peax/

Pass parameter to c3js tooltop after .generate() and before .load()

I'm trying to graph out metrics that don't have any relation to one another, so instead of plotting out the actual values, I've calculated an alternate set of values that are scaled between 0-1 like a percentage.
For example: [1, 2, 5] => [0.2, 0.4, 1]
So now I have 2 sets of data - the original and scaled versions. I have the scaled version plotting on to my graph just fine, but I want the tooltip to show the original value to the user. See what I mean?
I checked out http://c3js.org/samples/tooltip_format.html, which shows you can set tooltip as a function when you initially generate the C3 object. But I want to change the tooltip later on after I recalculate my original/scaled values and re-load() the graph.
All attempts I've made to explicitly change myGraph.tooltip.format.value = function (...) {...} after initially setting myGraph = C3.generate({...}) have been unsuccessful.
Any ideas how I can accomplish this without having to regenerate the graph from scratch every time?
You need to override internal.getTooltipContent
var data = ['data1', 30000, 20000, 10000, 40000, 15000, 250000];
// simple fake data
var fakeData = data.map(function (d, i) {
return i ? (d / 100) : d;
})
var chart = c3.generate({
data: {
columns: [
fakeData,
['data2', 100, 200, 100, 40, 150, 250]
],
}
});
// do code to take over mars and plant potatoes
// save the original
var originalGetTooltipContent = chart.internal.getTooltipContent;
chart.internal.getTooltipContent = function (data, defaultTitleFormat, defaultValueFormat, color) {
// we modified the first series, so let's change that alone back
var originalValue = {
id: data[0].id,
index: data[0].index,
name: data[0].name,
// unfaked
value: data[0].value * 100,
x: data[0].x
};
var originalValues = data.map(function (d, i) {
return i ? d : originalValue;
})
return originalGetTooltipContent.call(this, originalValues, defaultTitleFormat, defaultValueFormat, color)
}
I assume you are already doing something about the scaled y axis label?
Fiddle - http://jsfiddle.net/puf248en/
Thanks potatopeelings,
I did turn out solving this one by simply loading the form data in all at once, and then programmatically show/hide certain metrics. So that allowed me to use all the generate() options as intended.
Did try out your solution, and it seemed to do the trick till I found the simpler option. Thanks!

Dynamic styles in OpenLayers 3

In OpenLayers 2 it was possible to extend the style definition with dynamic parts - special functions that calculates a specific style value at render time. Is there an equivalent in OpenLayers 3?
Here sample code from OpenLayers 2:
var stdStyleMap = new OpenLayers.StyleMap({
"default": new OpenLayers.Style({
/* fixed value */
fillOpacity: 0.8,
/* value from server response */
fillColor: "${fillcolor}",
/* value calculated at render time */
pointRadius: "${getPointRadius}",
}, {
context: {
/* function that calculates the point radius */
getPointRadius: function(feature) {
if (feature.attributes && feature.attributes.pointRadius)
return feature.attributes.pointRadius;
else
return 5;
}
}})
});
Here's a good example of using custom styles for polygons from the Openlayers site.
But the following is an example that answers a question i posted... so, yay for both of us... maybe.
// we'd normally pass feature & resolution parameters to the function, but we're going to
// make this dynamic, so we'll return a style function for later use which will take those params.
DynamicStyleFunction = ( function( /* no feat/res yet!*/ ) {
/**
you really only get style are rendered upon simple geometries, not features. features are made of different geometry types, and styleFunctions are passed a feature that has its geometries rendered. in terms of styling vector geometries, you have only a few options. side note: if there's some feature you expect to see on the the map and it's not showing up, you probably haven't properly styled it. Or, maybe it hasn't been put it in a collection that is included in the source layer... which is a hiccup for a different day.
*/
// for any geometry that you want to be rendered, you'll want a style.
var styles = {};
var s = styles;
/**
an ol.layer.Vector or FeatureOverlay, renders those features in its source by applying Styles made of Strokes, Fills, and Images (made of strokes and fills) on top of the simple geometries which make up the features
Stroke styles get applied to ol.geom.GeometryType.LINE_STRING
MULTI_LINE_STRING can get different styling if you want
*/
var strokeLinesWhite = new ol.style.Stroke({
color: [255, 255, 255, 1], // white
width: 5,
})
var whiteLineStyle new ol.style.Style({
stroke: strokeLinesWhite
})
styles[ol.geom.GeometryType.LINE_STRING] = whiteLineStyle
/**
Polygon styles get applied to ol.geom.GeometryType.POLYGON
Polygons are gonna get filled. They also have Lines... so they can take stroke
*/
var fillPolygonBlue = new ol.style.Style({
fill: new ol.style.Fill({
color: [0, 153, 255, 1], // blue
})
})
var whiteOutlinedBluePolygon = new ol.style.Style({
stroke: strokeLinesWhite,
fill: fillPolygonBlue,
})
styles[ol.geom.GeometryType.POLYGON] = fillPolygonBlue
/**
Circle styles get applied to ol.geom.GeometryType.POINT
They're made with a radius and a fill, and the edge gets stroked...
*/
var smallRedCircleStyle = new ol.style.Style({
image: new ol.style.Circle({
radius: 5,
fill: new ol.style.Fill({
color: '#FF0000', // red... but i had to look it up
})
})
})
var whiteBigCircleWithBlueBorderStyle = new ol.style.Style({
image: new ol.style.Circle({
radius: 10,
fill: new ol.style.Fill({
color: '#FFFFFF' // i guessed it
})
}),
stroke: new.ol.style.Stroke({
color: '#0000FF', // blue
width: 5
})
})
// render all points as small red circles
styles[ol.geom.GeometryType.POINT] = smallRedCircleStyle
// if you pass an array as the style argument, every rendering of the feature will apply every defined style style rendered with the geometry as the argument. that can be a whole lot of rendering in a FeatureOverlay...
smallRedCircleStyle.setZIndex(Infinity)
whiteBigCircleWithBlueBorderStyle.setZIndex(Infinity -1) // that prob wouldn't work, but i hope it's instructive that you can tinker with styles
// so...
var bullseyePointStyle = [ smallRedCircleStyle, whiteBigCircleWithBlueBorderStyle ];
return function dynamicStyleFunction (feature, resolution){
// this is the actual function getting invoked on each function call
// do whatever you want with the feature/resolution.
if (Array.indexOf(feature.getKeys('thisIsOurBullseyeNode') > -1) {
return bullseyePointStyle
} else if (feature.getGeometryName('whiteBlueBox')){
return whiteOutlinedBluePolygon
} else {
return styles[feature.getGeometryName()]
}
}
})()
Yes there is style function that takes a feature and the current resolution, see some examples in the workshop: http://openlayers.org/workshop/vector/style.html

Categories