diff --git a/media/js/src/GraphEditor.jsx b/media/js/src/GraphEditor.jsx index 836b4d6c1..7f7920b9c 100644 --- a/media/js/src/GraphEditor.jsx +++ b/media/js/src/GraphEditor.jsx @@ -18,6 +18,7 @@ import RevenueElasticityEditor from './editors/RevenueElasticityEditor.jsx'; import OptimalChoiceCostMinimizingEditor from './editors/OptimalChoiceCostMinimizingEditor.jsx'; import TaxationLinearDemandEditor from './editors/TaxationLinearDemandEditor.jsx'; import TaxRevenueEditor from './editors/TaxRevenueEditor.jsx'; +import NegativeProductionExternalityProducerEditor from './editors/NegativeProductionExternalityProducerEditor.jsx'; import JXGBoard from './JXGBoard.jsx'; import GraphPane from './GraphPane.jsx'; @@ -330,6 +331,13 @@ export default class GraphEditor extends React.Component { {...commonEditorProps} {...this.props} />; + } else if (this.props.gType === 26) { + rightSide = + ; } const hasIntersection = ![ diff --git a/media/js/src/GraphViewer.jsx b/media/js/src/GraphViewer.jsx index c6c33ca50..7e164ed74 100644 --- a/media/js/src/GraphViewer.jsx +++ b/media/js/src/GraphViewer.jsx @@ -16,6 +16,8 @@ import OptimalChoiceConsumptionEditor from './editors/OptimalChoiceConsumption.j import CostFunctionsEditor from './editors/CostFunctionsEditor.jsx'; import OptimalChoiceCostMinimizingEditor from './editors/OptimalChoiceCostMinimizingEditor.jsx'; import TaxationLinearDemandEditor from './editors/TaxationLinearDemandEditor.jsx'; +import NegativeProductionExternalityProducerEditor from +'./editors/NegativeProductionExternalityProducerEditor.jsx'; import ExportGraphButton from './buttons/ExportGraphButton.jsx'; import ResetGraphButton from './buttons/ResetGraphButton.jsx'; @@ -330,7 +332,14 @@ export default class GraphViewer extends React.Component { {...commonViewerProps} {...this.props} />; - } + } else if (this.props.gType === 26) { + rightSide = + ; + } return (
diff --git a/media/js/src/JXGBoard.jsx b/media/js/src/JXGBoard.jsx index 8772debf3..f1044a9e7 100644 --- a/media/js/src/JXGBoard.jsx +++ b/media/js/src/JXGBoard.jsx @@ -570,6 +570,18 @@ export default class JXGBoard extends React.Component { xTicks = this.visibleTicks; yTicks = xTicks; break; + case 26: + xTicks = this.visibleTicks; + yTicks = xTicks; + break; + case 28: + xTicks = this.visibleTicks; + yTicks = xTicks; + break; + case 27: + xTicks = this.visibleTicks; + yTicks = xTicks; + break; default: xAxisLabel = options.gXAxisLabel ? options.gXAxisLabel : 'x'; yAxisLabel = options.gYAxisLabel ? options.gYAxisLabel : 'y'; diff --git a/media/js/src/editors/NegativeProductionExternalityProducerEditor.jsx b/media/js/src/editors/NegativeProductionExternalityProducerEditor.jsx new file mode 100644 index 000000000..48e646667 --- /dev/null +++ b/media/js/src/editors/NegativeProductionExternalityProducerEditor.jsx @@ -0,0 +1,137 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import RangeEditor from '../form-components/RangeEditor.jsx'; +import { handleFormUpdate } from '../utils.js'; + +export default class NegativeProductionExternalityProducerEditor extends React.Component { + render() { + const me = this; + + const modesLeft = [ + 'Negative Producer Externality', + 'Unregulated', + 'Welfare', + ]; + const modesRight = [ + 'Pigouvian Tax', + 'Pigouvian Tax (Welfare)', + ]; + + const radioButtons1 = modesLeft.map((optionTitle, idx) => +
+ + +
+ ); + + const radioButtons2 = modesRight.map(function(optionTitle, idx) { + const newIdx = idx + modesLeft.length; + return ( +
+ + +
+ ); + }); + + return ( + <> +
+
+ {radioButtons1} +
+
+ {radioButtons2} +
+
+ +
+ {this.props.displaySliders && ( + <> + + + + + + + + + + + )} +
+ + ); + } +} + +NegativeProductionExternalityProducerEditor.propTypes = { + gType: PropTypes.number.isRequired, + + gA1: PropTypes.number.isRequired, + gA2: PropTypes.number.isRequired, + gA3: PropTypes.number.isRequired, + gA4: PropTypes.number.isRequired, + gA5: PropTypes.number.isRequired, + gA6: PropTypes.number, + + gFunctionChoice: PropTypes.number.isRequired, + gToggle: PropTypes.bool.isRequired, + + displaySliders: PropTypes.bool.isRequired +}; diff --git a/media/js/src/graphUtils.js b/media/js/src/graphUtils.js index 3f9c06915..9c5cdf8d0 100644 --- a/media/js/src/graphUtils.js +++ b/media/js/src/graphUtils.js @@ -15,6 +15,9 @@ import { import { defaults as cobbDouglasDefaults } from './graphs/CobbDouglasGraph.js'; +import { + defaults as externalitiesDefaults +} from './graphs/NegativeProductionExternalityProducerGraph.js'; /** * Set defaults to the given state update object based on toggle and @@ -130,6 +133,8 @@ export const getDefaultGraphState = function(graphType, state) { }); } else if (graphType === 25) { Object.assign(state, linearDemandSupplySurplusDefaults[0]); + } else if (graphType >= 26 && graphType <= 28) { + Object.assign(state, externalitiesDefaults[0]); } return state; diff --git a/media/js/src/graphs/NegativeProductionExternalityProducerGraph.js b/media/js/src/graphs/NegativeProductionExternalityProducerGraph.js index 75c206e8d..6b4425b5f 100644 --- a/media/js/src/graphs/NegativeProductionExternalityProducerGraph.js +++ b/media/js/src/graphs/NegativeProductionExternalityProducerGraph.js @@ -1,6 +1,105 @@ -import {Graph} from './Graph.js'; +import {Graph, positiveRange} from './Graph.js'; + +export const defaults = [ + { + gXAxisMax: 1000, + gYAxisMax: 2500, + gA1: 500, + gA2: 2, + gA3: 50, + gA4: 2, + } +]; + +// Marginal Benefit +const mb = function(a) { + return a; +}; + +const mc = function(c, d, q) { + return c + d * q; +}; + +// External Marginal Cost +const emc = function(f, g, q) { + return f + g * q; +}; + +const pemc = function(f, g, p) { + return (p - f) / g; +}; + + +// Social Marginal Cost +const smc = function(c, d, f, g, q) { + return mc(c, d, q) + emc(f, g, q); +}; + +const psmc = function(c, d, f, g, p) { + return (p - c - f) / (d + g); +}; export class NegativeProductionExternalityProducerGraph extends Graph { + static getGraphPane(gFunctionChoice) { + return [ + { + label: 'Unregulated Output q*', + color: 'red', + value: 500 + }, + { + label: 'Socially Desirable Output qsoc', + color: 'orange', + value: 247.5 + }, + { + label: 'Market Price P*', + color: 'red', + value: 1500 + }, + ]; + } + make() { + const me = this; + + const mbLine = function() { + return mb(me.options.gA1); + }; + + this.l1 = this.board.create( + 'functiongraph', + [positiveRange(mbLine), 0, this.options.gXAxisMax], { + name: 'MB', + withLabel: true, + label: { + strokeColor: this.l1Color + }, + strokeWidth: 2, + strokeColor: this.l1Color, + fixed: true, + highlight: false + } + ); + + const mcLine = function(c, d, x) { + return mc(me.options.gA2, me.options.gA3, x); + }; + + this.l2 = this.board.create( + 'functiongraph', + [positiveRange(mcLine), 0, this.options.gXAxisMax], { + name: 'MC', + withLabel: true, + label: { + strokeColor: this.l2Color + }, + strokeWidth: 2, + strokeColor: this.l2Color, + fixed: true, + highlight: false + } + ); + } } export const mkNegativeProductionExternalityProducer = function(board, options) {