Increase size of one of the series in R highchart - r

I'm trying to show a line and % changes in a single highchart plot, but the changes are very little and It can't be seen in the plot. I made a simplified code to show my problem:
a <- c(300,200, 400, 10, 40, 80)
b <- c(0.8, 2, -2, -1.5, -1.1, 2)
d<-cbind(a,b)
dt <- seq(as.Date("2018-01-01"), as.Date("2018-01-06"), by = "days")
ts <- xts(d, dt )
highchart(type="stock") %>%
hc_add_series(ts$a,
type = "line",
color="black") %>%
hc_add_series(ts$b,
type = "lollipop",
color="red")
I need to increase the size of "ts$b" in the plot, how can I do it? I also tried with two axis, but It seems doesn't solve the problem.

I see two solutions to achieve that.
The first you mentioned - using two yAxis and manipulating their height and top distance.
Example JS code:
yAxis: [{
height: '90%',
opposite: false
},
{
visible: false,
top: '83%',
height: '15%',
}
]
Demo:
https://jsfiddle.net/BlackLabel/0826r7sh/
Another way is using a modified logarithmic axis. Negative values can't be plotted on a log axis, because by nature, the axis will only show positive values. In that case you need to use a custom extension according to the following thread:
Highcharts negative logarithmic scale solution stopped working
(function(H) {
H.addEvent(H.Axis, 'afterInit', function() {
const logarithmic = this.logarithmic;
if (logarithmic && this.options.custom.allowNegativeLog) {
// Avoid errors on negative numbers on a log axis
this.positiveValuesOnly = false;
// Override the converter functions
logarithmic.log2lin = num => {
const isNegative = num < 0;
let adjustedNum = Math.abs(num);
if (adjustedNum < 10) {
adjustedNum += (10 - adjustedNum) / 10;
}
const result = Math.log(adjustedNum) / Math.LN10;
return isNegative ? -result : result;
};
logarithmic.lin2log = num => {
const isNegative = num < 0;
let result = Math.pow(10, Math.abs(num));
if (result < 10) {
result = (10 * (result - 1)) / (10 - 1);
}
return isNegative ? -result : result;
};
}
});
}(Highcharts));
.
yAxis: {
type: 'logarithmic',
custom: {
allowNegativeLog: true
}
},
Demo
https://jsfiddle.net/BlackLabel/nw6osucm/

Related

Select points in line series

I'd like to use a modifier key with the left mouse button that will select the data inside the rectangle, rather than the zoom to that data. Is this possible? I cannot find a suitable API for it. Bonus points if there's a way to select data that falls inside a polygon (like a lasso tool).
Here's one example of completely custom ChartXY interactions.
Key points:
Default rectangle fit & zoom interactions are disabled.
Line series data is cached to a variable which can be used for custom statistics.
RectangleSeries is used for visualizing drag area on chart.
UI elements are used for displaying statistics of selected area.
ChartXY.onSeriesBackgroundMouseDrag event is used for hooking custom actions to user interactions.
Below you'll find a code snippet where dragging with left mouse button creates a rectangular area which shows highlighted X area and solved Y data range within.
Releasing the mouse button results in the full selected data points array being solved (length is logged to console).
const {
Point,
ColorRGBA,
SolidFill,
RadialGradientFill,
SolidLine,
translatePoint,
lightningChart,
UIOrigins,
UIElementBuilders,
UILayoutBuilders,
emptyFill
} = lcjs;
const { createProgressiveTraceGenerator } = xydata;
const chart = lightningChart()
.ChartXY()
// Disable default chart interactions with left mouse button.
.setMouseInteractionRectangleFit(false)
.setMouseInteractionRectangleZoom(false)
.setTitleFillStyle(emptyFill)
const axisX = chart.getDefaultAxisX()
const axisY = chart.getDefaultAxisY()
const lineSeries = chart.addLineSeries({
dataPattern: {
pattern: 'ProgressiveX',
},
})
// Generate test data set.
let dataSet
createProgressiveTraceGenerator()
.setNumberOfPoints(10 * 1000)
.generate()
.toPromise()
.then((data) => {
// Cache data set for analytics logic + add static data to series.
dataSet = data
lineSeries.add(data)
})
// Rectangle Series is used to display data selection area.
const rectangleSeries = chart.addRectangleSeries()
const rectangle = rectangleSeries
.add({ x1: 0, y1: 0, x2: 0, y2: 0 })
.setFillStyle(
new RadialGradientFill({
stops: [
{ offset: 0, color: ColorRGBA(255, 255, 255, 30) },
{ offset: 1, color: ColorRGBA(255, 255, 255, 60) },
],
}),
)
.setStrokeStyle(
new SolidLine({
thickness: 2,
fillStyle: new SolidFill({ color: ColorRGBA(255, 255, 255, 255) }),
}),
)
.dispose()
// UI elements are used to display information about the selected data points.
const uiInformationLayout = chart.addUIElement(UILayoutBuilders.Column, { x: axisX, y: axisY }).dispose()
const uiLabel0 = uiInformationLayout.addElement(UIElementBuilders.TextBox)
const uiLabel1 = uiInformationLayout.addElement(UIElementBuilders.TextBox)
// Add events for custom interactions.
chart.onSeriesBackgroundMouseDrag((_, event, button, startLocation) => {
// If not left mouse button, don't do anything.
if (button !== 0) return
// Translate start location and current location to axis coordinates.
const startLocationAxis = translatePoint(
chart.engine.clientLocation2Engine(startLocation.x, startLocation.y),
chart.engine.scale,
lineSeries.scale,
)
const curLocationAxis = translatePoint(
chart.engine.clientLocation2Engine(event.clientX, event.clientY),
chart.engine.scale,
lineSeries.scale,
)
// Place Rectangle figure between start location and current location.
rectangle.restore().setDimensions({
x1: startLocationAxis.x,
y1: startLocationAxis.y,
x2: curLocationAxis.x,
y2: curLocationAxis.y,
})
// * Gather analytics from actively selected data *
const xStart = Math.min(startLocationAxis.x, curLocationAxis.x)
const xEnd = Math.max(startLocationAxis.x, curLocationAxis.x)
// Selected Y range has to be solved from data set.
// NOTE: For top solve performance, results should be cached and only changes from previous selection area should be checked.
const { yMin, yMax } = solveDataRangeY(xStart, xEnd)
// Set UI labels text.
uiLabel0.setText(`X: [${xStart.toFixed(0)}, ${xEnd.toFixed(0)}]`)
uiLabel1.setText(`Y: [${yMin.toFixed(1)}, ${yMax.toFixed(1)}]`)
// Place UI layout above Rectangle.
uiInformationLayout
.restore()
.setOrigin(UIOrigins.LeftBottom)
.setPosition({ x: xStart, y: Math.max(startLocationAxis.y, curLocationAxis.y) })
})
chart.onSeriesBackgroundMouseDragStop((_, event, button, startLocation) => {
// If not left mouse button, don't do anything.
if (button !== 0) return
// Translate start location and current location to axis coordinates.
const startLocationAxis = translatePoint(
chart.engine.clientLocation2Engine(startLocation.x, startLocation.y),
chart.engine.scale,
lineSeries.scale,
)
const curLocationAxis = translatePoint(
chart.engine.clientLocation2Engine(event.clientX, event.clientY),
chart.engine.scale,
lineSeries.scale,
)
// Print selected data points to console.
const xStart = Math.max(0, Math.floor(Math.min(startLocationAxis.x, curLocationAxis.x)))
const xEnd = Math.min(dataSet.length - 1, Math.ceil(Math.max(startLocationAxis.x, curLocationAxis.x)))
const selectedDataPoints = dataSet.slice(xStart, xEnd)
console.log(`Selected ${selectedDataPoints.length} data points.`)
// Hide visuals.
rectangle.dispose()
uiInformationLayout.dispose()
})
// Logic for solving Y data range between supplied X range from active data set.
const solveDataRangeY = (xStart, xEnd) => {
// Reduce Y data min and max values within specified X range from data set.
// Note, this can be very heavy for large data sets - repeative calls should be avoided as much as possible for best performance.
let yMin = Number.MAX_SAFE_INTEGER
let yMax = -Number.MAX_SAFE_INTEGER
xStart = Math.max(0, Math.floor(xStart))
xEnd = Math.min(dataSet.length - 1, Math.ceil(xEnd))
for (let iX = xStart; iX < xEnd; iX += 1) {
const y = dataSet[iX].y
yMin = y < yMin ? y : yMin
yMax = y > yMax ? y : yMax
}
return { yMin, yMax }
}
<script src="https://unpkg.com/#arction/xydata#1.4.0/dist/xydata.iife.js"></script>
<script src="https://unpkg.com/#arction/lcjs#3.0.0/dist/lcjs.iife.js"></script>
There's many different directions to go with this kind of custom interactions, and while we can't cover every single one with an example, most of the logic should stay the same.

d3-geo-voronoi d3-tile polygon fill problem

I am trying to use d3-geo-voronoi to display vector tile data using d3-tile. My initial attempt in displaying the data, with fill set to "none" worked, which was very exiting!
Voronoi tile map without color fill
However, when I attempted to fill the polygons, some of the tiles were distorted.
Voronoi tile map with color fill
I've not been able to figure out why this is happening. I checked the svg's in the dom, and everything looks correct. The svg's are correct where there are no polygons, they are just not being rendered properly, possibly they are being covered up. Below is the code I used:
const d3 = require('d3');
const d3tile = require('d3-tile');
const d3geovoronoi = require('d3-geo-voronoi');
const vt2geojson = require('#mapbox/vt2geojson');
const pi = Math.PI,
tau = 2 * pi;
const width = Math.max(960, window.innerWidth),
height = Math.max(500, window.innerHeight);
const map = d3.select("body").append("div")
.attr("class", "map")
.style("width", width + "px")
.style("height", height + "px")
.on("mousemove", mousemoved);
let projection = d3.geoMercator()
.scale(1 / tau)
.translate([0, 0]);
let center = projection([-76.3, 38.794745]);
const tile = d3tile.tile()
.size([width, height]);
const zoom = d3.zoom()
.scaleExtent([1 << 15, 1 << 24])
.on("zoom", zoomed);
const svg = map.append("g")
.attr("pointer-events", "none")
.attr("class", "svg");
const info = map.append("g")
.attr("class", "info");
const ramp = d3.scaleLinear().domain([0.05,0.07]).interpolate(d3.interpolateHcl).range(['#34d8eb','#3a34eb']).unknown("#5c5752")
map.call(zoom)
.call(zoom.transform, d3.zoomIdentity
.translate(width / 2, height / 2)
.scale(1 << 21)
.translate(-center[0], -center[1]));
function zoomed() {
let transform = d3.event.transform;
let tiles = tile(transform);
let image = svg
.style("transform", stringify(tiles.scale, tiles.translate))
.selectAll(".tile")
.data(tiles, function(d) { return d; })
.enter().append("svg")
.attr("class", "tile")
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-width", "0.5")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.style("left", function(d) { return d[0] * 256 + "px"; })
.style("top", function(d) { return d[1] * 256 + "px"; })
.each(function(d) { this._xhr = render(d, this); });
projection
.scale(transform.k / tau)
.translate([transform.x, transform.y]);
}
function render(d, xnode) {
let k = Math.pow(2, d[2]) * 256;
vt2geojson({
uri: 'http://localhost:7800/public.r3sim_fort_temp/'+d[2]+'/'+d[0]+'/'+d[1]+'.pbf?properties=node,zeta,mask,bathymetry'
}, function (err, json) {
if (err) throw err;
d3.select(xnode)
.selectAll("path")
.data(d3geovoronoi.geoVoronoi().polygons(json).features)
.enter().append("path")
//.attr('fill', 'none')
.attr("fill", function(d) {return ramp(d.properties.site.properties.zeta)})
.attr("stroke", "#fff")
.attr("stroke-width", "0.5")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("d", d3.geoPath()
.projection(d3.geoMercator()
.scale(k / tau)
.translate([k / 2 - d[0] * 256, k / 2 - d[1] * 256])
.precision(0)));
})
}
function stringify(scale, translate) {
const k = scale / 256, r = scale % 1 ? Number : Math.round;
return "matrix3d(" + [k, 0, 0, 0, 0, k, 0, 0, 0, 0, k, 0, r(translate[0] * scale), r(translate[1] * scale), 0, 1 ] + ")";
}
function mousemoved() {
info.text(formatLocation(projection.invert(d3.mouse(this)), d3.zoomTransform(this).k));
}
function formatLocation(p, k) {
const format = d3.format("." + Math.floor(Math.log(k) / 2 - 2) + "f");
return (p[1] < 0 ? format(-p[1]) + "°S" : format(p[1]) + "°N") + " "
+ (p[0] < 0 ? format(-p[0]) + "°W" : format(p[0]) + "°E");
}
<!doctype html>
<head>
<meta charset="utf-8">
<title>D3 V5 Vector Tile Example</title>
<style>
body {
margin: 0;
}
.map {
background: #5c5752;
position: relative;
overflow: hidden;
}
.svg {
position: absolute;
will-change: transform;
}
.tile {
position: absolute;
width: 256px;
height: 256px;
}
.info {
position: absolute;
bottom: 10px;
left: 10px;
}
</style>
</head>
<body>
<script src="bundle.js"></script>
</body>
In this example I filled the polygons with varying color values. However, the exact same distortions occur if I use a single color value. The distortions are also always in the same place, if I reload all of the data.
I did a deeper dive into the data, and found the bad svg path, and then found the data related to it. It looks like d3.geo.voronoi is producing some bad coordinates, but the input data looks okay. Below are two printouts of node 1192. The first is the input geojson data, showing the coordinates, and the second is the voronoi geometry. The voronoi geometry contains longitude values in the eastern hemisphere (103.86...), which is way outside of the range of the data. I'm still trying to determine why these bad values are being produced. Again, the input coordinates look correct, but possibly it is other data that goes into the voronoi calculation?
1192
{…}
geometry: {…}
coordinates: (2) […]
0: -76.12801194190979
1: 38.78622954627738
length: 2
<prototype>: Array []
type: "Point"
<prototype>: Object { … }
properties: Object { node: 180407, zeta: "NaN", mask: "True", … }
type: "Feature"
<prototype>: Object { … }
1192 (11) […]
0: Array [ 103.86695733932268, -44.964779133003304 ]
1: Array [ -76.13308210176842, 38.75793814039401 ]
2: Array [ -76.13020999558496, 38.782688154120585 ]
3: Array [ -76.12890669699081, 38.78647064351637 ]
4: Array [ -76.12807302385534, 38.786723650244355 ]
5: Array [ -76.12754554182737, 38.78651000385868 ]
6: Array [ -76.12640847594942, 38.78408839960177 ]
7: Array [ -76.11435851540921, 38.636536130021334 ]
8: Array [ 103.858096036925, -39.00570100251519 ]
9: Array [ 103.860092112702, -39.367933188411186 ]
10: Array [ 103.86695733932268, -44.964779133003304 ]
length: 11
<prototype>: []

Detect if Lines Intersect in Google Charts or Plot.ly

I have seen scripts that claim to enter coordinates and it'll tell you if they intersect, but I have an array of X,Y values for a couple of "lines" but how do I cycle through the points to find out if they intersect?
I've included a photo of my graph and as you see, eventually my plots cross over, I just want to know if my values ever cross over (intersect).
How do I run through this to find out if any intersection ever occurs?
var Test = {
x: [8043, 10695, 13292, 17163, 20716, 25270],
y: [1000, 274, 100, 27.4, 10, 2.74],
fill: 'tozeroy',
type: 'scatter',
name: 'Test'
};
var Test2 = {
x: [8043, 10063, 12491, 16081, 19408, 23763],
y: [1000, 274, 100, 27.4, 10, 2.74],
fill: 'tozeroy',
type: 'scatter',
name: 'Test2'
};
var Test3 = {
x: [4700, 5943, 7143, 8841, 10366, 13452],
y: [1000, 274, 100, 27.4, 10, 2.74],
fill: 'tozeroy',
type: 'scatter',
name: 'Test3'
};
var data = [Test, Test2, Test3];
var layout = {
width: 700,
height: 700,
xaxis: {
type: 'log',
range: [3,5]
},
yaxis: {
type: 'log',
range: [-2,3]
}
};
Plotly.newPlot('myDiv', data,layout);
Path intercepts
This answer is a follow on from my answer to your most resent question.
The code snippet below will find the intercepts of the paths in the tables as structured in this questions example data using a modified intercept function from the answer link in may comment from aforementioned answer.
Note I am assuming that each table eg Test in your example data represents a curve (Path as a set of line segments) and that intercepts are not expected within a table but rather between tables.
Basic solution
It does this by checking each line segment in one table against each line segment in the other and storing all intercepts in an array.
Note that if a intercept is found at the start or end point of a line it may appear in the array of intercepts twice as the intercept test includes these points.
Note lines that are parallel, even if they have matching start and or end points will not count as intercepts.
The example is run against the example data and has a verbose console output to guide, if needed, you working through what ever data sets you are wrangling. The console logs can be removed without ill effect.
var Test = {
x: [8043, 10695, 13292, 17163, 20716, 25270],
y: [1000, 274, 100, 27.4, 10, 2.74],
fill: 'tozeroy',
type: 'scatter',
name: 'Test'
};
var Test2 = {
x: [8043, 10063, 12491, 16081, 19408, 23763],
y: [1000, 274, 100, 27.4, 10, 2.74],
fill: 'tozeroy',
type: 'scatter',
name: 'Test2'
};
var Test3 = {
x: [4700, 5943, 7143, 8841, 10366, 13452],
y: [1000, 274, 100, 27.4, 10, 2.74],
fill: 'tozeroy',
type: 'scatter',
name: 'Test3'
};
// Copy from here to end comment and place into you page (code base)
// lines outputting to the console eg console.log are just there to help you out
// and can be removed
const lineIntercepts = (() => {
const Point = (x, y) => ({x, y});
const Line = (p1, p2) => ({p1, p2});
const Vector = line => Point(line.p2.x - line.p1.x, line.p2.y - line.p1.y);
function interceptSegs(line1, line2) {
const a = Vector(line1), b = Vector(line2);
const c = a.x * b.y - a.y * b.x;
if (c) {
const e = Point(line1.p1.x - line2.p1.x, line1.p1.y - line2.p1.y);
const u = (a.x * e.y - a.y * e.x) / c;
if (u >= 0 && u <= 1) {
const u = (b.x * e.y - b.y * e.x) / c;
if (u >= 0 && u <= 1) {
return Point(line1.p1.x + a.x * u, line1.p1.y + a.y * u);
}
}
}
}
const PointFromTable = (t, idx) => Point(t.x[idx], t.y[idx]);
const LineFromTable = (t, idx) => Line(PointFromTable(t, idx++), PointFromTable(t, idx));
return function (table1, table2) {
const results = [];
var i = 0, j;
while (i < table1.x.length - 1) {
const line1 = LineFromTable(table1, i);
j = 0;
while (j < table2.x.length - 1) {
const line2 = LineFromTable(table2, j);
const point = interceptSegs(line1, line2);
if (point) {
results.push({
description: `'${table1.name}' line seg index ${i}-${i+1} intercepts '${table2.name}' line seg index ${j} - ${j+1}`,
// The description (line above) can be replaced
// with relevant data as follows
/* remove this line to include additional info per intercept
tableName1: table1.name,
tableName2: table2.name,
table_1_PointStartIdx: i,
table_1_PointEndIdx: i + 1,
table_2_PointStartIdx: j,
table_2_PointEndIdx: j + 1,
and remove this line */
x: point.x,
y: point.y,
});
}
j ++;
}
i++;
}
if (results.length) {
console.log("Found " + results.length + " intercepts for '" + table1.name + "' and '" + table2.name + "'");
console.log(results);
return results;
}
console.log("No intercepts found for '" + table1.name + "' and '" + table2.name + "'");
}
})();
// end of code
// Test and example code only from here down.
var res1 = lineIntercepts(Test, Test2);
var res2 = lineIntercepts(Test, Test3);
var res3 = lineIntercepts(Test2, Test3);
Using the above function
This bit of code illustrates how you extract intercepts from the function results
// find all the intercepts for the paths in tabels Test and Test2
const results = lineIntercepts(Test, Test2); // pass two tables
// If results not undefined then intercepts have been found
if (results) { // results is an array of found intercepts
// to get the point/s as there could be several
for (const intercept of results) { // loop over every intercept
// a single intercept coordinate
const x = intercept.x; // get x
const y = intercept.y; // get y
}
}
Better solutions
The paths look very much like they are a plot of some function thus there are even simpler solutions.
Rather than list out lines of code, I will direct you towards graphing calculators in case you are unaware of such useful time savers. They would have solved your problem in the time it takes to enter the data (by copy&paste thats not very long)
Online graphing calculators example apps Geogebra and Desmos and many more.

R Markdown - Highcharter - Animate graph when visible, rather on page load?

I am writing a report in R Markdown, it contains multiple animated highcharts.
The animations work fine, however they all run when the html page loads (after knitting), instead of when the user scrolls to it, so essentially the animation is pointless as the user never sees it.
An example of an animated chart is at the bottom of this question.
Is there a way to make it animate when it appears? All the examples I have found use jsfiddle and I am using R Markdown.
Many thanks
library(dplyr)
library(stringr)
library(purrr)
n <- 5
set.seed(123)
df <- data.frame(x = seq_len(n) - 1) %>%
mutate(
y = 10 + x + 10 * sin(x),
y = round(y, 1),
z = (x*y) - median(x*y),
e = 10 * abs(rnorm(length(x))) + 2,
e = round(e, 1),
low = y - e,
high = y + e,
value = y,
name = sample(fruit[str_length(fruit) <= 5], size = n),
color = rep(colors, length.out = n),
segmentColor = rep(colors2, length.out = n)
)
hcs <- c("line") %>%
map(create_hc)
hcs
Ok, I worked out how to do it myself, going to post the answer here in case someone stumbles across this post in the future.
First of all, I found NOTHING on how to do this in R.
So, I decided to do this in JS, AFTER I had knitted the R Markdown document to HTML, as it wouldn't work in R Markdown.
Once it is a HTML file, open it in TextEdit or Notepad, and add the following code just before one of the charts:
<script>
(function (H) {
var pendingRenders = [];
// https://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport/7557433#7557433
function isElementInViewport(el) {
var rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (
window.innerHeight ||
document.documentElement.clientHeight
) &&
rect.right <= (
window.innerWidth ||
document.documentElement.clientWidth
)
);
}
H.wrap(H.Series.prototype, 'render', function deferRender(proceed) {
var series = this,
renderTo = this.chart.container.parentNode;
// It is appeared, render it
if (isElementInViewport(renderTo) || !series.options.animation) {
proceed.call(series);
// It is not appeared, halt renering until appear
} else {
pendingRenders.push({
element: renderTo,
appear: function () {
proceed.call(series);
}
});
}
});
function recalculate() {
pendingRenders.forEach(function (item) {
if (isElementInViewport(item.element)) {
item.appear();
H.erase(pendingRenders, item);
}
});
}
if (window.addEventListener) {
['DOMContentLoaded', 'load', 'scroll', 'resize']
.forEach(function (eventType) {
addEventListener(eventType, recalculate, false);
});
}
}(Highcharts));
</script>
The charts then animate when you scroll to them, rather than when you open the HTML file.
Note: The JSFIDDLE I got the code from was from here:
https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/studies/appear/

paperjs - How to apply brightness/contrast like effects on the raster image?

I have tried to apply the image adjustment options using paper.js, but it will only apply to the fillcolor.
Does anyone know how to apply brightness, contrast or other image adjustments to the raster image?
For example:
var url = 'http://images.com/q.jpg';
var raster = new paper.Raster(url);
raster.brightness = .5;
Are there any pre-defined functions available for image adjustment in paper.js?
Nope, but you can play with blend modes or opacity.
I would advise using specialized WebGL libraries like glfx or webgl-filter for image effects (I didn't try them, but they seem powerful).
function reDrawImage(lightness = 10,contrast = 1.5) {
const raster = paper.project.activeLayer.children[0] as paper.Raster
const ctx: CanvasRenderingContext2D = currentRaster.getContext(true)
const imageData = ctx.getImageData(0, 0, currentRaster.width, currentRaster.height)
for (let i = 0; i < imageData.data.length; i += 4) {
imageData.data[i] = saturate_cast(imageData.data[i] * (contrast + lightness));
imageData.data[i+1] = saturate_cast(imageData.data[i+1] * (contrast + lightness));
imageData.data[i+2] = saturate_cast(imageData.data[i+2] * (contrast + lightness));
}
raster.setImageData(imageData, new Point(0, 0))
}
function saturate_cast(num: number) {
if (num > 255) {
return 255
}
if (num < 0) {
return 0
}
return num
}

Resources